├── .bowerrc
├── .csslintrc
├── .eslintrc
├── .gitignore
├── .npmrc
├── .travis.yml
├── Dockerfile
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── karma.conf.js
├── karma.conf.sauce.js
├── karma.shared.js
├── package.json
├── public
├── .eslintrc
├── README.md
├── bower_components
│ └── fxa-relier-client
│ │ ├── fxa-relier-client.js
│ │ ├── fxa-relier-client.min.js
│ │ └── fxa-relier-client.min.js.map
├── favicon.ico
├── fonts
│ ├── clearsans
│ │ ├── clearsans-bold.eot
│ │ ├── clearsans-bold.svg
│ │ ├── clearsans-bold.ttf
│ │ ├── clearsans-bold.woff
│ │ ├── clearsans-bolditalic.eot
│ │ ├── clearsans-bolditalic.svg
│ │ ├── clearsans-bolditalic.ttf
│ │ ├── clearsans-bolditalic.woff
│ │ ├── clearsans-italic.eot
│ │ ├── clearsans-italic.svg
│ │ ├── clearsans-italic.ttf
│ │ ├── clearsans-italic.woff
│ │ ├── clearsans-light.eot
│ │ ├── clearsans-light.svg
│ │ ├── clearsans-light.ttf
│ │ ├── clearsans-light.woff
│ │ ├── clearsans-medium.eot
│ │ ├── clearsans-medium.svg
│ │ ├── clearsans-medium.ttf
│ │ ├── clearsans-medium.woff
│ │ ├── clearsans-mediumitalic.eot
│ │ ├── clearsans-mediumitalic.svg
│ │ ├── clearsans-mediumitalic.ttf
│ │ ├── clearsans-mediumitalic.woff
│ │ ├── clearsans-regular.eot
│ │ ├── clearsans-regular.svg
│ │ ├── clearsans-regular.ttf
│ │ ├── clearsans-regular.woff
│ │ ├── clearsans-thin.eot
│ │ ├── clearsans-thin.svg
│ │ ├── clearsans-thin.ttf
│ │ └── clearsans-thin.woff
│ └── firasans
│ │ ├── firasans-bold.eot
│ │ ├── firasans-bold.svg
│ │ ├── firasans-bold.ttf
│ │ ├── firasans-bold.woff
│ │ ├── firasans-bolditalic.eot
│ │ ├── firasans-bolditalic.svg
│ │ ├── firasans-bolditalic.ttf
│ │ ├── firasans-bolditalic.woff
│ │ ├── firasans-book.eot
│ │ ├── firasans-book.svg
│ │ ├── firasans-book.ttf
│ │ ├── firasans-book.woff
│ │ ├── firasans-bookitalic.eot
│ │ ├── firasans-bookitalic.svg
│ │ ├── firasans-bookitalic.ttf
│ │ ├── firasans-bookitalic.woff
│ │ ├── firasans-eight.eot
│ │ ├── firasans-eight.svg
│ │ ├── firasans-eight.ttf
│ │ ├── firasans-eight.woff
│ │ ├── firasans-eightitalic.eot
│ │ ├── firasans-eightitalic.svg
│ │ ├── firasans-eightitalic.ttf
│ │ ├── firasans-eightitalic.woff
│ │ ├── firasans-extrabold.eot
│ │ ├── firasans-extrabold.svg
│ │ ├── firasans-extrabold.ttf
│ │ ├── firasans-extrabold.woff
│ │ ├── firasans-extrabolditalic.eot
│ │ ├── firasans-extrabolditalic.svg
│ │ ├── firasans-extrabolditalic.ttf
│ │ ├── firasans-extrabolditalic.woff
│ │ ├── firasans-extralight.eot
│ │ ├── firasans-extralight.svg
│ │ ├── firasans-extralight.ttf
│ │ ├── firasans-extralight.woff
│ │ ├── firasans-extralightitalic.eot
│ │ ├── firasans-extralightitalic.svg
│ │ ├── firasans-extralightitalic.ttf
│ │ ├── firasans-extralightitalic.woff
│ │ ├── firasans-four.eot
│ │ ├── firasans-four.svg
│ │ ├── firasans-four.ttf
│ │ ├── firasans-four.woff
│ │ ├── firasans-fouritalic.eot
│ │ ├── firasans-fouritalic.svg
│ │ ├── firasans-fouritalic.ttf
│ │ ├── firasans-fouritalic.woff
│ │ ├── firasans-hair.eot
│ │ ├── firasans-hair.svg
│ │ ├── firasans-hair.ttf
│ │ ├── firasans-hair.woff
│ │ ├── firasans-hairitalic.eot
│ │ ├── firasans-hairitalic.svg
│ │ ├── firasans-hairitalic.ttf
│ │ ├── firasans-hairitalic.woff
│ │ ├── firasans-heavy.eot
│ │ ├── firasans-heavy.svg
│ │ ├── firasans-heavy.ttf
│ │ ├── firasans-heavy.woff
│ │ ├── firasans-heavyitalic.eot
│ │ ├── firasans-heavyitalic.svg
│ │ ├── firasans-heavyitalic.ttf
│ │ ├── firasans-heavyitalic.woff
│ │ ├── firasans-italic.eot
│ │ ├── firasans-italic.svg
│ │ ├── firasans-italic.ttf
│ │ ├── firasans-italic.woff
│ │ ├── firasans-light.eot
│ │ ├── firasans-light.svg
│ │ ├── firasans-light.ttf
│ │ ├── firasans-light.woff
│ │ ├── firasans-lightitalic.eot
│ │ ├── firasans-lightitalic.svg
│ │ ├── firasans-lightitalic.ttf
│ │ ├── firasans-lightitalic.woff
│ │ ├── firasans-medium.eot
│ │ ├── firasans-medium.svg
│ │ ├── firasans-medium.ttf
│ │ ├── firasans-medium.woff
│ │ ├── firasans-mediumitalic.eot
│ │ ├── firasans-mediumitalic.svg
│ │ ├── firasans-mediumitalic.ttf
│ │ ├── firasans-mediumitalic.woff
│ │ ├── firasans-regular.eot
│ │ ├── firasans-regular.svg
│ │ ├── firasans-regular.ttf
│ │ ├── firasans-regular.woff
│ │ ├── firasans-regularitalic.eot
│ │ ├── firasans-regularitalic.svg
│ │ ├── firasans-regularitalic.ttf
│ │ ├── firasans-regularitalic.woff
│ │ ├── firasans-semibold.eot
│ │ ├── firasans-semibold.svg
│ │ ├── firasans-semibold.ttf
│ │ ├── firasans-semibold.woff
│ │ ├── firasans-semibolditalic.eot
│ │ ├── firasans-semibolditalic.svg
│ │ ├── firasans-semibolditalic.ttf
│ │ ├── firasans-semibolditalic.woff
│ │ ├── firasans-thin.eot
│ │ ├── firasans-thin.svg
│ │ ├── firasans-thin.ttf
│ │ ├── firasans-thin.woff
│ │ ├── firasans-thinitalic.eot
│ │ ├── firasans-thinitalic.svg
│ │ ├── firasans-thinitalic.ttf
│ │ ├── firasans-thinitalic.woff
│ │ ├── firasans-two.eot
│ │ ├── firasans-two.svg
│ │ ├── firasans-two.ttf
│ │ ├── firasans-two.woff
│ │ ├── firasans-twoitalic.eot
│ │ ├── firasans-twoitalic.svg
│ │ ├── firasans-twoitalic.ttf
│ │ ├── firasans-twoitalic.woff
│ │ ├── firasans-ultra.eot
│ │ ├── firasans-ultra.svg
│ │ ├── firasans-ultra.ttf
│ │ ├── firasans-ultra.woff
│ │ ├── firasans-ultraitalic.eot
│ │ ├── firasans-ultraitalic.svg
│ │ ├── firasans-ultraitalic.ttf
│ │ ├── firasans-ultraitalic.woff
│ │ ├── firasans-ultralight.eot
│ │ ├── firasans-ultralight.svg
│ │ ├── firasans-ultralight.ttf
│ │ ├── firasans-ultralight.woff
│ │ ├── firasans-ultralightitalic.eot
│ │ ├── firasans-ultralightitalic.svg
│ │ ├── firasans-ultralightitalic.ttf
│ │ └── firasans-ultralightitalic.woff
├── img
│ ├── cardicons-sprite.svg
│ ├── firefox.png
│ ├── management
│ │ ├── history.svg
│ │ ├── pay-methods.svg
│ │ ├── profile.svg
│ │ └── subs.svg
│ ├── mgmt-nav-sprite.svg
│ ├── pay-method-sprite.svg
│ ├── spinnerlight.png
│ ├── spinnerlight@2x.png
│ ├── spinnerwhite.png
│ └── spinnerwhite@2x.png
├── index.html
├── js
│ ├── actions
│ │ ├── api.js
│ │ ├── braintree.js
│ │ ├── management.js
│ │ ├── notifications.js
│ │ ├── pay-methods.js
│ │ ├── processing.js
│ │ ├── subscriptions.js
│ │ ├── transaction.js
│ │ └── user.js
│ ├── apps
│ │ ├── management
│ │ │ ├── app.jsx
│ │ │ └── main.jsx
│ │ └── transaction
│ │ │ ├── app.jsx
│ │ │ └── main.jsx
│ ├── components
│ │ ├── card-form.jsx
│ │ ├── card-input.jsx
│ │ ├── input-error.jsx
│ │ ├── modal.jsx
│ │ ├── notification-list.jsx
│ │ ├── notification.jsx
│ │ ├── pay-method-choice.jsx
│ │ ├── pay-method-drop-down.jsx
│ │ ├── pay-method-icon.jsx
│ │ ├── pay-method-item.jsx
│ │ ├── pay-method-list.jsx
│ │ ├── product-detail.jsx
│ │ ├── spinner.jsx
│ │ ├── submit-button.jsx
│ │ ├── subscription-list.jsx
│ │ └── subscription.jsx
│ ├── constants
│ │ ├── action-types.js
│ │ └── error-codes.js
│ ├── data-store.js
│ ├── products.js
│ ├── reducers
│ │ ├── app.js
│ │ ├── index.js
│ │ ├── management.js
│ │ ├── processing.js
│ │ ├── transaction.js
│ │ └── user.js
│ ├── settings.js
│ ├── tracking.js
│ ├── utils.jsx
│ └── views
│ │ ├── management.jsx
│ │ ├── management
│ │ ├── add-pay-method.jsx
│ │ ├── confirm-del-pay-method.jsx
│ │ ├── del-pay-method.jsx
│ │ ├── history.jsx
│ │ ├── my-account.jsx
│ │ ├── pay-methods.jsx
│ │ └── subscriptions.jsx
│ │ ├── shared
│ │ ├── braintree-token.jsx
│ │ ├── sign-in.jsx
│ │ └── sign-out.jsx
│ │ ├── transaction.jsx
│ │ └── transaction
│ │ ├── complete-payment.jsx
│ │ ├── product-pay-chooser.jsx
│ │ └── product-pay.jsx
├── management.html
├── media
│ ├── README.md
│ └── img
│ │ ├── tabzilla-static-high-res.png
│ │ └── tabzilla-static.png
├── scss
│ ├── _base.scss
│ ├── _buttons.scss
│ ├── _complete-payment.scss
│ ├── _email.scss
│ ├── _forms.scss
│ ├── _global.scss
│ ├── _json-table.scss
│ ├── _management.scss
│ ├── _mgmt-nav-sprite.scss
│ ├── _modal.scss
│ ├── _notification.scss
│ ├── _pay-method-drop-down.scss
│ ├── _pay-method-item.scss
│ ├── _pay-method-list.scss
│ ├── _pay-method-sprite.scss
│ ├── _pay-methods.scss
│ ├── _product.scss
│ ├── _signed-out.scss
│ ├── _spinner.scss
│ ├── _subscriptions.scss
│ ├── _tooltip.scss
│ ├── _transaction.scss
│ ├── _typography.scss
│ ├── _utils.scss
│ ├── common.scss
│ ├── config
│ │ ├── dev
│ │ │ └── _settings.scss
│ │ └── local
│ │ │ └── _settings.scss
│ ├── email.scss
│ ├── inc
│ │ ├── mixins.scss
│ │ └── vars.scss
│ ├── lib
│ │ ├── normalize.scss
│ │ └── tabzilla.scss
│ ├── management.scss
│ └── transaction.scss
└── svg
│ ├── amex.svg
│ ├── diners.svg
│ ├── discover.svg
│ ├── history.svg
│ ├── jcb.svg
│ ├── maestro.svg
│ ├── mastercard.svg
│ ├── pay-methods.svg
│ ├── profile.svg
│ ├── subs.svg
│ └── visa.svg
├── styleguide
├── jsx
│ ├── card-form.jsx
│ ├── modal.jsx
│ └── spinner.jsx
├── pages
│ ├── buttons.md
│ ├── card-form.md
│ ├── index.md
│ ├── modal.md
│ ├── spinner.md
│ └── typography.md
└── templates
│ ├── base-styleguide.html
│ ├── buttons-spinner.html
│ ├── buttons.html
│ ├── jsx.html
│ └── typography.html
├── tasks
├── .eslintrc
├── abideCompile.js
├── abideCreate.js
├── abideExtract.js
├── abideMerge.js
├── clean.js
├── cog.js
├── concurrent.js
├── copy.js
├── csslint.js
├── devserver.js
├── eslint.js
├── gh-pages.js
├── karma.js
├── sass.js
├── svg_sprite.js
├── watch.js
├── webpack-dev-server.js
└── webpack.js
├── tests
├── .eslintrc
├── .gitkeep
├── actions
│ ├── test.api.js
│ ├── test.braintree.js
│ ├── test.notification-actions.js
│ ├── test.pay-method-actions.js
│ ├── test.processing-actions.js
│ ├── test.subs-actions.js
│ ├── test.transaction.js
│ └── test.user-actions.js
├── apps
│ ├── test.management-app.jsx
│ └── test.transaction-app.jsx
├── components
│ ├── test.card-form.jsx
│ ├── test.modal.jsx
│ ├── test.notification-list.jsx
│ ├── test.notification.jsx
│ ├── test.pay-method-choice.jsx
│ ├── test.pay-method-drop-down.jsx
│ ├── test.pay-method-icon.jsx
│ ├── test.product-detail.jsx
│ ├── test.spinner.jsx
│ └── test.submit-button.js
├── helpers.jsx
├── reducers
│ ├── test.app-reducer.js
│ ├── test.management-reducer.js
│ ├── test.processing-reducer.js
│ ├── test.trans-reducer.js
│ └── test.user-reducer.js
├── test-loader.js
├── test.gh-pages-config.js
├── test.product-data.js
├── test.tracking.js
├── test.utils.jsx
└── views
│ ├── test.braintree-token.jsx
│ ├── test.complete-payment.jsx
│ ├── test.del-pay-method-confirmation.jsx
│ ├── test.history.jsx
│ ├── test.management.jsx
│ ├── test.pay-methods.jsx
│ ├── test.product-pay-chooser.jsx
│ ├── test.product-pay.jsx
│ ├── test.sign-in.jsx
│ ├── test.sign-out.jsx
│ ├── test.subscriptions.jsx
│ └── test.transaction.jsx
└── webpack.config.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "public/bower_components/"
3 | }
4 |
--------------------------------------------------------------------------------
/.csslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "adjoining-classes": false,
3 | "box-model": false,
4 | "box-sizing": false,
5 | "bulletproof-font-face": false,
6 | "compatible-vendor-prefixes": false,
7 | "duplicate-properties": 2,
8 | "fallback-colors": false,
9 | "font-faces": false,
10 | "font-sizes": false,
11 | "floats": false,
12 | "ids": false,
13 | "important": false,
14 | "outline-none": false,
15 | "qualified-headings": false,
16 | "regex-selectors": false,
17 | "unique-headings": false,
18 | "universal-selector": false,
19 | "unqualified-attributes": false,
20 | "zero-units": 2
21 | }
22 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | // Babel parser required to support the spread operator
3 | // in jsx. See: https://github.com/eslint/espree/issues/116
4 | "parser": "babel-eslint",
5 | "env": {
6 | "node": true,
7 | "browser": true,
8 | "es6": true,
9 | },
10 | "plugins": [
11 | "react"
12 | ],
13 | "ecmaFeatures": {
14 | "arrowFunctions": true,
15 | "blockBindings": true,
16 | "classes": true,
17 | "destructuring": true,
18 | "defaultParams": true,
19 | "jsx": true,
20 | "modules": true,
21 | "restParams": true,
22 | "spread": true,
23 | },
24 | "globals": {
25 | "exports": false,
26 | "module": false,
27 | "require": false,
28 | "FxaRelierClient": false,
29 | },
30 | "rules": {
31 | "block-scoped-var": 0,
32 | "camelcase": 0,
33 | "comma-dangle": [2, "always-multiline"],
34 | "comma-style": [2, "last"],
35 | "curly": [2, "all"],
36 | "dot-notation": [2, {"allowKeywords": true}],
37 | "eqeqeq": [2, "allow-null"],
38 | "guard-for-in": 0,
39 | "jsx-quotes": 1,
40 | "max-len": [2, 80, 2, {ignoreComments: true}],
41 | "new-cap": [2, {"capIsNewExceptions": ["Deferred"]}],
42 | "no-bitwise": 2,
43 | "no-caller": 2,
44 | "no-cond-assign": [2, "except-parens"],
45 | "no-debugger": 2,
46 | "no-empty": 2,
47 | "no-eval": 2,
48 | "no-extend-native": 2,
49 | "no-extra-parens": 0,
50 | "no-extra-semi": 2,
51 | "no-irregular-whitespace": 2,
52 | "no-iterator": 2,
53 | "no-loop-func": 2,
54 | "no-multi-str": 2,
55 | "no-new": 2,
56 | "no-plusplus": 2,
57 | "no-proto": 2,
58 | "no-redeclare": 0,
59 | "no-script-url": 2,
60 | "no-sequences": 2,
61 | "no-undef": 2,
62 | "no-underscore-dangle": 0,
63 | "no-unused-vars": 2,
64 | "no-with": 2,
65 | "quotes": [2, "single", "avoid-escape"],
66 | "quote-props": [1, "consistent-as-needed"],
67 | "react/display-name": 0,
68 | "react/jsx-boolean-value": 1,
69 | "react/jsx-no-undef": 1,
70 | "react/jsx-sort-props": 0,
71 | "react/jsx-sort-prop-types": 1,
72 | "react/jsx-uses-react": 1,
73 | "react/jsx-uses-vars": 1,
74 | "react/no-did-mount-set-state": 1,
75 | "react/no-did-update-set-state": 1,
76 | "react/no-multi-comp": 1,
77 | "react/no-unknown-property": 1,
78 | "react/prop-types": 1,
79 | "react/react-in-jsx-scope": 1,
80 | "react/self-closing-comp": 1,
81 | "react/sort-comp": 1,
82 | "react/wrap-multilines": 1,
83 | "semi": [2, "always"],
84 | "space-infix-ops": 0,
85 | "strict": [2, "never"],
86 | "valid-typeof": 2,
87 | "wrap-iife": [2, "inside"]
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bower_components
3 | .grunt
4 | styleguide/build
5 | styleguide/jsx-bundles
6 | npm-debug.log
7 | public/dist/*
8 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | save-prefix=''
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: false
3 | node_js:
4 | - '0.10'
5 | before_script:
6 | - npm rebuild
7 | - export DISPLAY=:99.0
8 | - sh -e /etc/init.d/xvfb start
9 | script: npm test
10 | notifications:
11 | irc:
12 | channels:
13 | - irc.mozilla.org#payments
14 | on_success: change
15 | on_failure: always
16 | after_success: npm run-script publish-docker > /dev/null 2>&1
17 | env:
18 | global:
19 | - secure: a1fGjKDKp+35F1XbuRYFSePOVgDeUAOHpfVJzrV8PnAl6ONKOr49HQocIAtF4ALevcbYS05IS47RES9Cp+7jkon10Ry2KrFR1or0euWBEukuTwL8n3rc9AhnOBDOp5RO2CWvweocFzLGa5MM4aCCw0wnmhmAUOGDWrwG5DMTwpY=
20 | - secure: L64ENqh7CpB8ziEa4DRzwTUVdsXix5nBBWzXAiZlGQQKrydEEiHpVE7/tYIN/k61IbAISguJuEGahPElOReWF4LxfYSmlSPZeh3QdLO+Pc9HilnSemP7YBXEKXLck32z1bbmV/NGxBM8nA0qpYOcSDg+ljWEmCgpp1eQaXqxwCk=
21 | - secure: SGYIOsqecNHtrWTrHQnibhkZ8NbizuDIbfiNBcZ0raSz7hvGpLrZeXPwuBSdJRUkJ9D6/Q2SZEdCq2niklddXBArAiTg3FiywHL/0Eo/CVHxgX0UQOHemJJx+xAAHkvNFSsXvrGkcjuA3hqKe7iNCBjp044PLgrnY7KNfpEXFsA=
22 | - secure: O7Uc/wE/Nyu0CLTH7EaxYcwYhQC8XoNVe7QcYXk5xcBUhTcuDtxD1uY0fVC7deNEYKDlL1Bu88dqOXN/n2cyExST+gr1L8FKlHXLhkuF35U4HuP+ybagjRtc7oAFOKXR0PFthczJ9wJJ7B+gkAo2JDkjp0goAN5f6Mz47tfDLfc=
23 | - secure: MDZohwJmwnGhXvCDs9ozZqVoT6E+xBQ/XJmh1KiRr4KHztcrOzPRa+yxQkxXDkGLVnVWI6/mgFd8XB49J7f7tVEl3mdOjQPa7RdjF3jIJlmedE3UVlfRpperkM/vHHrbqX0uXwPm2B+HIKH2DPu3eQ+bQQOuFvfYeZ/AYcpkI/4=
24 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM cogniteev/echo
2 |
3 | COPY public /srv/payments-ui
4 | COPY .git/logs/HEAD /srv/payments-ui/git-rev.txt
5 | VOLUME /srv/payments-ui
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
2 |
3 | If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "moz-payments-ui",
3 | "dependencies": {
4 | "fxa-relier-client": "https://github.com/mozilla/fxa-relier-client.git#0.0.8"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | var karmaConfig = require('./karma.shared');
2 |
3 | module.exports = function (config) {
4 | config.set(karmaConfig);
5 | };
6 |
--------------------------------------------------------------------------------
/karma.conf.sauce.js:
--------------------------------------------------------------------------------
1 | var defaults = require('lodash.defaults');
2 | var karmaConfig = require('./karma.shared');
3 | var browsers = require('mozilla-payments-saucelabs-browsers');
4 |
5 |
6 | module.exports = function (config) {
7 |
8 | if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) {
9 | console.log('Make sure the SAUCE_USERNAME and ' +
10 | 'SAUCE_ACCESS_KEY environment variables are set.');
11 | throw new Error('Missing SAUCE_USERNAME and SAUCE_ACCESS_KEY env vars');
12 | }
13 |
14 | karmaConfig.plugins.push('karma-sauce-launcher');
15 | karmaConfig.reporters.push('saucelabs');
16 |
17 | config.set(defaults({
18 | // Increase timeout in case connection in CI is slow
19 | browserDisconnectTimeout: 10000,
20 | browserDisconnectTolerance: 2,
21 | browserNoActivityTimeout: 30000,
22 | captureTimeout: 120000,
23 | customLaunchers: browsers,
24 | browsers: Object.keys(browsers),
25 | port: 9876,
26 | sauceLabs: {
27 | testName: 'Payments UI Unit Tests',
28 | recordScreenshots: false,
29 | connectOptions: {
30 | username: process.env.SAUCE_USERNAME,
31 | accessKey: process.env.SAUCE_ACCESS_KEY,
32 | tunnelIdentifier: 'autoGeneratedTunnelID',
33 | },
34 | },
35 | }, karmaConfig));
36 | };
37 |
--------------------------------------------------------------------------------
/karma.shared.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* This is the shared karma config */
4 | var merge = require('lodash.merge');
5 | var webpackConfig = require('./webpack.config');
6 |
7 | var newWebpackConfig = merge({}, webpackConfig);
8 | // Remove the bits from the shared config
9 | // that we don't want for tests.
10 | delete newWebpackConfig.output;
11 | delete newWebpackConfig.entry;
12 |
13 | newWebpackConfig.plugins = [];
14 |
15 | // Expose the right kind of source map for test-loader.js
16 | newWebpackConfig.devtool = 'inline-source-map';
17 |
18 | module.exports = {
19 | basePath: '',
20 | browsers: ['Firefox'],
21 | colors: true,
22 | frameworks: [
23 | 'mocha',
24 | 'chai',
25 | 'sinon',
26 | ],
27 | files: [
28 | 'tests/test-loader.js',
29 | ],
30 | preprocessors: {
31 | 'tests/test-loader.js': ['webpack', 'sourcemap'],
32 | },
33 | reporters: ['mocha'],
34 | plugins: [
35 | 'karma-sinon',
36 | 'karma-mocha',
37 | 'karma-mocha-reporter',
38 | 'karma-chai',
39 | 'karma-firefox-launcher',
40 | 'karma-sourcemap-loader',
41 | 'karma-webpack',
42 | ],
43 | singleRun: true,
44 | webpack: newWebpackConfig,
45 | webpackServer: {
46 | noInfo: true,
47 | quiet: true,
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/public/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | },
6 | "rules": {
7 | "global-strict": 0,
8 | "strict": [2, "never"]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/public/README.md:
--------------------------------------------------------------------------------
1 | # About the public dir
2 |
3 | All content under public will be served.
4 |
5 | ## What not to put here
6 |
7 | * Anything that shouldn't be made public.
8 | * Test files or unecessary cruft.
9 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/favicon.ico
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-bold.eot
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-bold.ttf
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-bold.woff
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-bolditalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-bolditalic.eot
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-bolditalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-bolditalic.ttf
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-bolditalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-bolditalic.woff
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-italic.eot
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-italic.ttf
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-italic.woff
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-light.eot
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-light.ttf
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-light.woff
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-medium.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-medium.eot
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-medium.ttf
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-medium.woff
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-mediumitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-mediumitalic.eot
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-mediumitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-mediumitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-mediumitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-mediumitalic.woff
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-regular.eot
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-regular.ttf
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-regular.woff
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-thin.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-thin.eot
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-thin.ttf
--------------------------------------------------------------------------------
/public/fonts/clearsans/clearsans-thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/clearsans/clearsans-thin.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-bold.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-bold.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-bold.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-bolditalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-bolditalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-bolditalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-bolditalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-bolditalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-bolditalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-book.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-book.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-book.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-book.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-book.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-book.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-bookitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-bookitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-bookitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-bookitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-bookitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-bookitalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-eight.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-eight.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-eight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-eight.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-eight.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-eight.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-eightitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-eightitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-eightitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-eightitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-eightitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-eightitalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extrabold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extrabold.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extrabold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extrabold.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extrabold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extrabold.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extrabolditalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extrabolditalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extrabolditalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extrabolditalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extrabolditalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extrabolditalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extralight.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extralight.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extralight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extralight.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extralight.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extralight.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extralightitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extralightitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extralightitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extralightitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-extralightitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-extralightitalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-four.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-four.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-four.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-four.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-four.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-four.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-fouritalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-fouritalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-fouritalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-fouritalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-fouritalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-fouritalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-hair.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-hair.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-hair.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-hair.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-hair.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-hair.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-hairitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-hairitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-hairitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-hairitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-hairitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-hairitalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-heavy.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-heavy.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-heavy.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-heavy.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-heavy.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-heavy.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-heavyitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-heavyitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-heavyitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-heavyitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-heavyitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-heavyitalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-italic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-italic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-italic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-light.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-light.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-light.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-lightitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-lightitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-lightitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-lightitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-lightitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-lightitalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-medium.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-medium.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-medium.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-medium.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-mediumitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-mediumitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-mediumitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-mediumitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-mediumitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-mediumitalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-regular.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-regular.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-regular.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-regularitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-regularitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-regularitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-regularitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-regularitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-regularitalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-semibold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-semibold.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-semibold.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-semibold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-semibold.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-semibolditalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-semibolditalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-semibolditalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-semibolditalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-semibolditalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-semibolditalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-thin.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-thin.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-thin.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-thin.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-thinitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-thinitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-thinitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-thinitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-thinitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-thinitalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-two.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-two.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-two.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-two.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-two.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-two.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-twoitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-twoitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-twoitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-twoitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-twoitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-twoitalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultra.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultra.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultra.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultra.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultra.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultra.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultraitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultraitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultraitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultraitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultraitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultraitalic.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultralight.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultralight.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultralight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultralight.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultralight.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultralight.woff
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultralightitalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultralightitalic.eot
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultralightitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultralightitalic.ttf
--------------------------------------------------------------------------------
/public/fonts/firasans/firasans-ultralightitalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/fonts/firasans/firasans-ultralightitalic.woff
--------------------------------------------------------------------------------
/public/img/firefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/img/firefox.png
--------------------------------------------------------------------------------
/public/img/management/history.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/public/img/management/pay-methods.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/public/img/management/profile.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/img/management/subs.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/public/img/spinnerlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/img/spinnerlight.png
--------------------------------------------------------------------------------
/public/img/spinnerlight@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/img/spinnerlight@2x.png
--------------------------------------------------------------------------------
/public/img/spinnerwhite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/img/spinnerwhite.png
--------------------------------------------------------------------------------
/public/img/spinnerwhite@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/img/spinnerwhite@2x.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Payments UI
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/public/js/actions/api.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 |
3 | import * as defaultSettings from 'settings';
4 |
5 |
6 | export function fetch(request,
7 | {jquery=$, settings=defaultSettings, csrfToken} = {}) {
8 | request = Object.assign({
9 | dataType: 'json',
10 | headers: {},
11 | xhrFields: {},
12 | }, request);
13 |
14 | request.url = settings.apiPrefix + request.url;
15 |
16 | if (settings.allowCORSRequests) {
17 | // This will send cookies on Access Control requests which is needed
18 | // for sessions. It really only applies to newer XHR2 transports which
19 | // have stricter security. We only need CORS for webpack hot
20 | // reloading development.
21 | request.xhrFields.withCredentials = true;
22 | }
23 |
24 | if (typeof csrfToken === 'undefined') {
25 | // This exists to stop someone from forgetting to pass in the stored CSRF
26 | // token.
27 | throw new Error(
28 | 'You must set a CSRF token value; ' +
29 | 'set it to false to fetch a resource without a token'
30 | );
31 | }
32 |
33 | if (csrfToken) {
34 | request.headers['X-CSRFToken'] = csrfToken;
35 | } else {
36 | console.warn('making request to ' + request.url +
37 | ' but no CSRF token has been set');
38 | }
39 |
40 | return jquery.ajax(request);
41 | }
42 |
--------------------------------------------------------------------------------
/public/js/actions/braintree.js:
--------------------------------------------------------------------------------
1 | import braintree from 'braintree-web';
2 |
3 | import * as errorCodes from 'constants/error-codes';
4 | import * as notificationActions from './notifications';
5 |
6 |
7 | export function tokenizeCreditCard({dispatch, braintreeToken, creditCard,
8 | callback,
9 | BraintreeClient=braintree.api.Client}) {
10 |
11 | if (typeof creditCard.number === 'undefined' ||
12 | typeof creditCard.cvv === 'undefined' ||
13 | typeof creditCard.expiration === 'undefined') {
14 | console.error('not a complete card object:', creditCard);
15 | throw new Error('Invalid card object');
16 | }
17 |
18 | var client = new BraintreeClient({
19 | clientToken: braintreeToken,
20 | });
21 |
22 | client.tokenizeCard({
23 | number: creditCard.number,
24 | expirationDate: creditCard.expiration,
25 | cvv: creditCard.cvv,
26 | }, (err, nonce) => {
27 | if (err) {
28 | console.error('Braintree tokenization error:', err);
29 | dispatch(notificationActions.showError(
30 | {errorCode: errorCodes.BRAINTREE_TOKENIZATION_ERROR}));
31 | } else {
32 | callback(nonce);
33 | }
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/public/js/actions/management.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 |
3 |
4 | export function showMyAccount() {
5 | return {
6 | type: actionTypes.SHOW_MY_ACCOUNT,
7 | };
8 | }
9 |
10 | export function showSignIn() {
11 | return {
12 | type: actionTypes.SHOW_SIGN_IN,
13 | };
14 | }
15 |
16 | export function showSignOut() {
17 | return {
18 | type: actionTypes.SHOW_SIGN_OUT,
19 | };
20 | }
21 |
22 | export function showPayMethods() {
23 | return {
24 | type: actionTypes.SHOW_PAY_METHODS,
25 | };
26 | }
27 |
28 | export function showAddPayMethod() {
29 | return {
30 | type: actionTypes.SHOW_ADD_PAY_METHOD,
31 | };
32 | }
33 |
34 | export function showDelPayMethod() {
35 | return {
36 | type: actionTypes.SHOW_DEL_PAY_METHOD,
37 | };
38 | }
39 |
40 | export function showConfirmDelPayMethod(payMethodUri) {
41 | return {
42 | type: actionTypes.SHOW_CONFIRM_DEL_PAY_METHOD,
43 | payMethodUri: payMethodUri,
44 | };
45 | }
46 |
47 | export function showHistory() {
48 | return {
49 | type: actionTypes.SHOW_HISTORY,
50 | };
51 | }
52 |
53 | export function showSubscriptions() {
54 | return {
55 | type: actionTypes.SHOW_SUBS,
56 | };
57 | }
58 |
59 | export function closeModal() {
60 | return {
61 | type: actionTypes.CLOSE_MODAL,
62 | };
63 | }
64 |
--------------------------------------------------------------------------------
/public/js/actions/notifications.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 | import * as errorCodes from 'constants/error-codes';
3 |
4 | import { gettext } from 'utils';
5 |
6 |
7 | export function _addNotification({ text, errorCode,
8 | type='info', userDismissable,
9 | autoHide } = {}) {
10 | return {
11 | type: actionTypes.ADD_NOTIFICATION,
12 | data: { autoHide, text, type, userDismissable, errorCode },
13 | };
14 | }
15 |
16 | export function showInfo({text, ...opts} = {}) {
17 | opts.type = 'info';
18 |
19 | if (!text) {
20 | throw new Error('An info notification requires text');
21 | } else {
22 | opts.text = text;
23 | }
24 |
25 | // Default info type to being hidden on delay.
26 | if (typeof opts.autoHide === 'undefined') {
27 | opts.autoHide = true;
28 | }
29 |
30 | console.log('Showing info notification', opts);
31 | return _addNotification({...opts});
32 | }
33 |
34 | export function showError({text, ...opts} = {}) {
35 | opts.type = 'error';
36 |
37 | if (!opts.errorCode || typeof errorCodes[opts.errorCode] === 'undefined') {
38 | throw new Error('An error notification requires an errorCode ' +
39 | 'defined in constants/error-codes');
40 | }
41 |
42 | if (!text) {
43 | opts.text = gettext('Internal error. Please try again later.');
44 | } else {
45 | opts.text = text;
46 | }
47 |
48 | // Default to error notification being userDismissable.
49 | if (typeof opts.userDismissable === 'undefined') {
50 | opts.userDismissable = true;
51 | }
52 |
53 | console.log('Showing error notification', opts);
54 | return _addNotification({...opts});
55 | }
56 |
57 | /*
58 | * The expectation is that this is called by the notificationList
59 | * automatically (after a timeout) or on a user click. It shouldn't
60 | * be necessary to call this directly.
61 | */
62 | export function removeNotification(id, {delay} = {}) {
63 | var data = {
64 | type: actionTypes.REMOVE_NOTIFICATION,
65 | id: id,
66 | };
67 | if (delay) {
68 | // Uses timeoutScheduler middleware.
69 | data.meta = {delay: delay};
70 | }
71 | return data;
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/public/js/actions/processing.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 |
3 |
4 | export function beginProcessing(processingId) {
5 | return processingAction(processingId, actionTypes.BEGIN_PROCESSING);
6 | }
7 |
8 |
9 | export function stopProcessing(processingId) {
10 | return processingAction(processingId, actionTypes.STOP_PROCESSING);
11 | }
12 |
13 |
14 | function processingAction(processingId, actionType) {
15 | if (!processingId) {
16 | throw new Error('processingId cannot be empty');
17 | }
18 | return {
19 | type: actionType,
20 | processingId: processingId,
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/public/js/apps/management/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 |
4 | import { default as ManagementApp } from './app';
5 | import dataStore from 'data-store';
6 |
7 |
8 | React.render((
9 |
10 | {() => }
11 |
12 | ), document.getElementById('placeholder'));
13 |
--------------------------------------------------------------------------------
/public/js/apps/transaction/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 |
4 | import tracking from 'tracking';
5 |
6 | import { default as TransactionApp } from './app';
7 | import dataStore from 'data-store';
8 |
9 |
10 | tracking.init();
11 |
12 | React.render((
13 |
14 | {function() {
15 | return ;
16 | }}
17 |
18 | ), document.body);
19 |
--------------------------------------------------------------------------------
/public/js/components/input-error.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import cx from 'classnames';
3 |
4 |
5 | export default class InputError extends Component {
6 |
7 | static propTypes = {
8 | errorMessage: PropTypes.string.isRequired,
9 | errorModifier: PropTypes.oneOf(['center', 'right', 'left']),
10 | }
11 |
12 | render() {
13 | var { errorMessage, ...toolTipAttrs } = this.props;
14 | var errorClass = cx([
15 | 'tooltip',
16 | this.props.errorModifier || 'left',
17 | ]);
18 | return (
19 | {errorMessage}
21 | );
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/public/js/components/modal.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { gettext } from 'utils';
3 | import cx from 'classnames';
4 |
5 |
6 | export default class Modal extends Component {
7 |
8 | static propTypes = {
9 | children: PropTypes.object.isRequired,
10 | handleClose: PropTypes.func.isRequired,
11 | title: PropTypes.string,
12 | }
13 |
14 | onClose = (e) => {
15 | var targetClassName = e.target.getAttribute('class') || '';
16 | var classes = targetClassName.split(' ');
17 | // Only deal with closing the window if the event
18 | // came from the backdrop or the close link.
19 | if (classes.length > 0 &&
20 | (classes.indexOf('modal') > -1 ||
21 | classes.indexOf('close') > -1)) {
22 | e.preventDefault();
23 | e.stopPropagation();
24 | this.props.handleClose();
25 | }
26 | }
27 |
28 | render() {
29 | var classes = cx(['modal', {active: true}]);
30 |
31 | return (
32 |
33 |
34 |
40 |
41 | {this.props.children}
42 |
43 |
44 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/public/js/components/notification-list.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react/addons';
2 | import Notification from 'components/notification';
3 |
4 | var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
5 |
6 | export default class NotificationList extends Component {
7 |
8 | static propTypes = {
9 | TransitionGroup: PropTypes.node,
10 | // notifications is an Mapable array of 2 item arrays
11 | // e.g: [[key, notificationObj], [key, notificationObj], ...]
12 | notifications: PropTypes.array.isRequired,
13 | removeNotification: PropTypes.func.isRequired,
14 | }
15 |
16 | static defaultProps = {
17 | TransitionGroup: ReactCSSTransitionGroup,
18 | }
19 |
20 | render() {
21 | var CSSTransitionGroup = this.props.TransitionGroup;
22 | var items = this.props.notifications.map((item) => {
23 | var [key, val] = item;
24 |
25 | return (
26 |
30 | );
31 | });
32 |
33 | return (
34 |
38 | {items}
39 |
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/js/components/notification.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { gettext } from 'utils';
3 |
4 | import cx from 'classnames';
5 |
6 |
7 | export default class Notification extends Component {
8 |
9 | static propTypes = {
10 | autoHide: PropTypes.bool,
11 | key: PropTypes.string.isRequired,
12 | removeNotification: PropTypes.func.isRequired,
13 | text: PropTypes.string.isRequired,
14 | type: React.PropTypes.oneOf(['info', 'error']),
15 | userDismissable: React.PropTypes.bool,
16 | }
17 |
18 | static defaultProps = {
19 | autoHide: true,
20 | type: 'info',
21 | userDismissable: false,
22 | }
23 |
24 | componentDidMount() {
25 | if (this.props.userDismissable === false &&
26 | this.props.autoHide === true) {
27 | this.props.removeNotification(this.props.key, {delay: 5000});
28 | }
29 | }
30 |
31 | handleDismissClick = (e) => {
32 | e.preventDefault();
33 | this.props.removeNotification(this.props.key);
34 | }
35 |
36 | render() {
37 | var classes = cx('notification', this.props.type,
38 | {autohide: this.props.autoHide});
39 | return (
40 |
41 | {this.props.text}
42 | { this.props.userDismissable ?
43 |
46 | {gettext('Dismiss')}
47 | : null }
48 |
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/public/js/components/pay-method-icon.jsx:
--------------------------------------------------------------------------------
1 | import cx from 'classnames';
2 | import React, { Component, PropTypes } from 'react';
3 |
4 | // Just a convenience mapping for payMethods from payMethod-validator
5 | // to shorted classes used in CSS.
6 | const payMethodTypeMap = {
7 | 'american-express': 'amex',
8 | 'diners-club': 'diners',
9 | 'master-card': 'mastercard',
10 | };
11 |
12 | export default class PayMethodIcon extends Component {
13 |
14 | static propTypes = {
15 | payMethodType: PropTypes.oneOf([
16 | 'amex',
17 | 'american-express',
18 | 'diners-club',
19 | 'discover',
20 | 'jcb',
21 | 'maestro',
22 | 'mastercard',
23 | 'master-card',
24 | 'visa',
25 | ]),
26 | }
27 |
28 | render() {
29 | // This is only displayed if a payMethodType is passed-in.
30 | var payMethodType = this.props.payMethodType;
31 | var payMethodClassName = cx([
32 | 'pay-method-icon',
33 | 'pmtype-' + (payMethodTypeMap[payMethodType] || payMethodType),
34 | ]);
35 | return payMethodType ? : null;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/public/js/components/pay-method-item.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import PayMethodIcon from 'components/pay-method-icon';
3 |
4 |
5 | export default class PayMethodItem extends Component {
6 |
7 | static propTypes = {
8 | checked: PropTypes.bool.isRequired,
9 | inputType: PropTypes.string.isRequired,
10 | onChangeHandler: PropTypes.func.isRequired,
11 | payMethod: PropTypes.shape({
12 | id: PropTypes.number,
13 | resource_uri: PropTypes.string,
14 | truncated_id: PropTypes.string,
15 | type_name: PropTypes.string,
16 | }),
17 | }
18 |
19 | static defaultProps = {
20 | inputType: 'radio',
21 | }
22 |
23 | render() {
24 | var payMethod = this.props.payMethod;
25 | var payMethodType = payMethod.type_name.toLowerCase();
26 | var payMethodText = '●●●● ●●●● ●●●● ' + payMethod.truncated_id;
27 | var inputId = 'paymethod-' + payMethod.id;
28 |
29 | return (
30 |
31 |
32 |
40 |
{payMethodText}
41 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/public/js/components/pay-method-list.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import PayMethodItem from 'components/pay-method-item';
3 |
4 | import cx from 'classnames';
5 |
6 |
7 | export default class PayMethodList extends Component {
8 |
9 | static propTypes = {
10 | cssModifier: PropTypes.string,
11 | onPayMethodChange: PropTypes.func.isRequired,
12 | payMethods: PropTypes.arrayOf(
13 | PropTypes.shape({
14 | id: PropTypes.number,
15 | resource_uri: PropTypes.string,
16 | truncated_id: PropTypes.string,
17 | type_name: PropTypes.string,
18 | })
19 | ).isRequired,
20 | }
21 |
22 | render() {
23 | var payMethods = this.props.payMethods;
24 | var payMethodList = [];
25 | for (var i = 0; i < payMethods.length; i += 1) {
26 | var { checked, ...payMethod } = payMethods[i];
27 | payMethodList.push((
28 |
29 |
35 |
36 | ));
37 | }
38 |
39 | var classes = cx('pay-method-list', this.props.cssModifier);
40 |
41 | return (
42 |
43 | {payMethodList}
44 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/public/js/components/product-detail.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import * as products from 'products';
3 | import { gettext } from 'utils';
4 |
5 | export default class ProductDetail extends Component {
6 |
7 | static propTypes = {
8 | productId: PropTypes.string.isRequired,
9 | userDefinedAmount: PropTypes.string,
10 | }
11 |
12 | render() {
13 |
14 | var productData = products.get(this.props.productId);
15 | var recurrence = (productData.recurrence === 'monthly' ?
16 | {gettext('per month')}
: '');
17 | var price;
18 |
19 | // TODO: localize/format prices with currency symbol.
20 | if (productData.price && Object.keys(productData.price).length) {
21 | console.log('Showing configured price for product', productData.id);
22 | price = productData.price.en;
23 | } else {
24 | console.log('Showing user defined amount for product',
25 | productData.id);
26 | var decimalPrice = parseFloat(this.props.userDefinedAmount);
27 | price = '$' + parseFloat(Math.round(decimalPrice * 100) / 100).toFixed(2);
28 | }
29 |
30 | return (
31 |
32 |
{productData.seller.name.en}
33 |
{productData.description.en}
34 |
{price}
35 | {recurrence}
36 |
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/js/components/spinner.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { gettext } from 'utils';
3 |
4 |
5 | export default class Spinner extends Component {
6 |
7 | static propTypes = {
8 | text: PropTypes.string.isRequired,
9 | }
10 |
11 | static defaultProps = {
12 | text: gettext('Loading'),
13 | }
14 |
15 | render() {
16 | return (
17 |
18 |
19 |
{this.props.text}
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/public/js/components/submit-button.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import cx from 'classnames';
3 |
4 | import { gettext } from 'utils';
5 |
6 |
7 | export default class SubmitButton extends Component {
8 |
9 | static propTypes = {
10 | content: PropTypes.string,
11 | cssModifier: PropTypes.string,
12 | isDisabled: PropTypes.bool,
13 | showSpinner: PropTypes.bool,
14 | }
15 |
16 | static defaultProps = {
17 | cssModifier: null,
18 | content: gettext('Submit'),
19 | }
20 |
21 | render() {
22 | var { isDisabled, content, showSpinner, ...buttonAttrs } = this.props;
23 |
24 | var buttonClassNames = cx({
25 | spinner: showSpinner,
26 | }, this.props.cssModifier);
27 |
28 | // If we're showing the spinner we want the
29 | // button to be automagically disabled.
30 | if (showSpinner) {
31 | isDisabled = true;
32 | }
33 |
34 | return (
35 | {content}
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/public/js/components/subscription-list.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | import { gettext } from 'utils';
4 | import Spinner from 'components/spinner';
5 | import Subscription from 'components/subscription';
6 |
7 |
8 | export default class SubscriptionList extends Component {
9 |
10 | static propTypes = {
11 | payMethods: PropTypes.array,
12 | subscriptions: PropTypes.array,
13 | }
14 |
15 | static defaultProps = {
16 | subscriptions: null,
17 | }
18 |
19 | render() {
20 | console.log('subscriptions:', this.props.subscriptions);
21 |
22 | if (this.props.subscriptions === null) {
23 | return ;
24 | } else {
25 |
26 | var subs = [];
27 | this.props.subscriptions.forEach((data) => {
28 | subs.push(
29 |
30 |
32 |
33 | );
34 | });
35 |
36 | if (subs.length) {
37 | return (
38 |
41 | );
42 | } else {
43 | return {gettext("You haven't subscribed to anything yet.")}
;
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/public/js/components/subscription.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | import PayMethodDropDown from 'components/pay-method-drop-down';
4 | import { gettext } from 'utils';
5 | import * as products from 'products';
6 |
7 |
8 | export default class Subscription extends Component {
9 |
10 | static propTypes = {
11 | payMethods: PropTypes.array.isRequired,
12 | paymethod: PropTypes.string.isRequired,
13 | seller_product: PropTypes.shape({
14 | public_id: PropTypes.string,
15 | }).isRequired,
16 | showNav: PropTypes.bool,
17 | showNextPayment: PropTypes.bool,
18 | }
19 |
20 | static defaultProps = {
21 | showNav: true,
22 | showNextPayment: true,
23 | }
24 |
25 | render() {
26 | var productData = products.get(this.props.seller_product.public_id);
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
{productData.seller.name.en}
34 |
{productData.description.en}
35 |
36 | {gettext('Monthly price')} {productData.price.en}
37 | {this.props.showNextPayment ?
38 |
39 | {gettext('Next payment')} PLACEHOLDER : null}
40 |
41 |
42 | {this.props.showNav ?
43 |
44 |
{gettext('Change payment account')}
46 | {this.props.payMethods.length ?
47 |
:
52 |
{gettext('No payment methods available')}
53 | }
54 |
{gettext('Cancel subscription')}
55 |
: null}
56 |
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/public/js/constants/action-types.js:
--------------------------------------------------------------------------------
1 | export const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
2 | export const ADD_PAY_METHOD = 'ADD_PAY_METHOD';
3 | export const APP_ERROR = 'APP_ERROR';
4 | export const BEGIN_PROCESSING = 'BEGIN_PROCESSING';
5 | export const CLOSE_MODAL = 'CLOSE_MODAL';
6 | export const COMPLETE_TRANSACTION = 'COMPLETE_TRANSACTION';
7 | export const CREDIT_CARD_SUBMISSION_ERRORS = 'CREDIT_CARD_SUBMISSION_ERRORS';
8 | export const DEL_PAY_METHOD = 'DEL_PAY_METHOD';
9 | export const GOT_BRAINTREE_TOKEN = 'GOT_BRAINTREE_TOKEN';
10 | export const GOT_CSRF_TOKEN = 'GOT_CSRF_TOKEN';
11 | export const GOT_PAY_METHODS = 'GOT_PAY_METHODS';
12 | export const GOT_SUBS_BY_PAY_METHOD = 'GOT_SUBS_BY_PAY_METHOD';
13 | export const GOT_USER_SUBS = 'GOT_USER_SUBS';
14 | export const GOT_USER_TRANSACTIONS = 'GOT_USER_TRANSACTIONS';
15 | export const LOADING_SUBS_BY_PAY_METHOD = 'LOADING_SUBS_BY_PAY_METHOD';
16 | export const LOADING_USER_SUBS = 'LOADING_USER_SUBS';
17 | export const LOADING_USER_TRANSACTIONS = 'LOADING_USER_TRANSACTIONS';
18 | export const PAY_WITH_NEW_CARD = 'PAY_WITH_NEW_CARD';
19 | export const REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION';
20 | export const SHOW_ADD_PAY_METHOD = 'SHOW_ADD_PAY_METHOD';
21 | export const SHOW_CONFIRM_DEL_PAY_METHOD = 'SHOW_CONFIRM_DEL_PAY_METHOD';
22 | export const SHOW_DEL_PAY_METHOD = 'SHOW_DEL_PAY_METHOD';
23 | export const SHOW_HISTORY = 'SHOW_HISTORY';
24 | export const SHOW_MY_ACCOUNT = 'SHOW_MY_ACCOUNT';
25 | export const SHOW_PAY_METHODS = 'SHOW_PAY_METHODS';
26 | export const SHOW_SIGN_IN = 'SHOW_SIGN_IN';
27 | export const SHOW_SIGN_OUT = 'SHOW_SIGN_OUT';
28 | export const SHOW_SUBS = 'SHOW_SUBS';
29 | export const STOP_PROCESSING = 'STOP_PROCESSING';
30 | export const USER_SIGNED_IN = 'USER_SIGNED_IN';
31 | export const USER_SIGNED_OUT = 'USER_SIGNED_OUT';
32 |
--------------------------------------------------------------------------------
/public/js/constants/error-codes.js:
--------------------------------------------------------------------------------
1 | export const ALREADY_SUBSCRIBED = 'ALREADY_SUBSCRIBED';
2 | export const API_SIGN_IN_FAILURE = 'API_SIGN_IN_FAILURE';
3 | export const API_SIGN_OUT_FAILURE = 'API_SIGN_OUT_FAILURE';
4 | export const BRAINTREE_TOKENIZATION_ERROR = 'BRAINTREE_TOKENIZATION_ERROR';
5 | export const BRAINTREE_TOKEN_GET_FAILED = 'BRAINTREE_TOKEN_GET_FAILED';
6 | export const FXA_SIGN_IN_FAILURE = 'FXA_SIGN_IN_FAILURE';
7 | export const FXA_TOKEN_SIGN_IN_FAILURE = 'FXA_TOKEN_SIGN_IN_FAILURE';
8 | export const ONE_TIME_PAYMENT_FAILED = 'ONE_TIME_PAYMENT_FAILED';
9 | export const PAY_METHOD_DELETION_FAILED = 'PAY_METHOD_DELETION_FAILED';
10 | export const PAY_METHOD_GET_FAILED = 'PAY_METHOD_GET_FAILED';
11 | export const PRODUCT_ID_INVALID = 'PRODUCT_ID_INVALID';
12 | export const SUBS_BY_PAY_METHOD_GET_FAILED = 'SUBS_BY_PAY_METHOD_GET_FAILED';
13 | export const SUBS_GET_FAILED = 'SUBS_GET_FAILED';
14 | export const SUB_CREATION_FAILED = 'SUB_CREATION_FAILED';
15 | export const SUB_UPDATE_FAILED = 'SUB_UPDATE_FAILED';
16 | export const TRANSACTIONS_GET_FAILED = 'TRANSACTIONS_GET_FAILED';
17 |
--------------------------------------------------------------------------------
/public/js/data-store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 |
4 | import rootReducer from 'reducers';
5 |
6 |
7 | function logger({ getState }) {
8 | return next => action => {
9 | if (typeof action.type === 'undefined') {
10 | console.error('action.type is undefined.',
11 | 'Check that the action is defined in constants/action-types.js');
12 | }
13 | console.info('redux: dispatching', action);
14 | const result = next(action);
15 | console.log('redux: state after', getState());
16 | return result;
17 | };
18 | }
19 |
20 |
21 | /**
22 | * Schedules actions with { meta: { delay: N } } to be delayed
23 | * by N milliseconds.
24 | * Makes `dispatch` return a function to cancel the timeout in this case.
25 | * http://rackt.github.io/redux/docs/advanced/Middleware.html#seven-examples
26 | */
27 | const timeoutScheduler = store => next => action => { // eslint-disable-line
28 | if (!action.meta || !action.meta.delay) {
29 | return next(action);
30 | }
31 |
32 | console.log('Scheduling action with timeout', action);
33 | let timeoutId = setTimeout(
34 | () => {
35 | console.log('Running after timeout', action.meta.delay);
36 | next(action);
37 | },
38 | action.meta.delay
39 | );
40 |
41 | return function cancel() {
42 | clearTimeout(timeoutId);
43 | };
44 | };
45 |
46 |
47 | const createStoreWithMiddleware = applyMiddleware(
48 | timeoutScheduler,
49 | thunk,
50 | logger
51 | )(createStore);
52 |
53 |
54 | export function createReduxStore() {
55 | const store = createStoreWithMiddleware(rootReducer);
56 |
57 | if (module.hot) {
58 | // Enable Webpack hot module replacement for reducers
59 | // See: https://github.com/rackt/react-redux/releases/tag/v2.0.0
60 | module.hot.accept('reducers', () => {
61 | const nextRootReducer = require('reducers');
62 | store.replaceReducer(nextRootReducer);
63 | });
64 | }
65 |
66 | return store;
67 | }
68 |
69 | export default createReduxStore();
70 |
--------------------------------------------------------------------------------
/public/js/products.js:
--------------------------------------------------------------------------------
1 | const productData = {
2 | 'mozilla-concrete-brick': require('json!mozilla-concrete-brick'),
3 | 'mozilla-concrete-mortar': require('json!mozilla-concrete-mortar'),
4 | 'mozilla-foundation-donation': require('json!mozilla-foundation-donation'),
5 | 'mozilla-foundation-recurring-donation':
6 | require('json!mozilla-foundation-recurring-donation'),
7 | };
8 |
9 | export default productData;
10 |
11 |
12 | export function get(id) {
13 | if (!productData[id]) {
14 | throw new Error('Invalid product: ' + id);
15 | }
16 | return productData[id];
17 | }
18 |
--------------------------------------------------------------------------------
/public/js/reducers/app.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 | import { getId } from 'utils';
3 |
4 |
5 | export const initialAppState = {
6 | csrfToken: null,
7 | // Notification is a mappable array e.g:
8 | // [[key, notification], [key, notification], ...]
9 | notifications: [],
10 | };
11 |
12 |
13 | export default function app(state, action) {
14 | switch (action.type) {
15 | case actionTypes.GOT_CSRF_TOKEN:
16 | return Object.assign({}, state, {
17 | csrfToken: action.csrfToken,
18 | });
19 | case actionTypes.ADD_NOTIFICATION:
20 | var newNotifications = state.notifications.slice(0);
21 | var notificationMap = new Map(newNotifications);
22 | var notification = notificationMap.set(getId(), action.data);
23 | newNotifications = [...notificationMap];
24 | return Object.assign({}, state, {
25 | notifications: newNotifications,
26 | });
27 | case actionTypes.REMOVE_NOTIFICATION:
28 | var newNotifications = state.notifications.slice(0);
29 | var notificationMap = new Map(newNotifications);
30 | var notification = notificationMap.get(action.id);
31 | if (notification) {
32 | console.log('Removing', notification, 'from notifications');
33 | notificationMap.delete(action.id);
34 | newNotifications = [...notificationMap];
35 | } else {
36 | console.warn("Can't remove non-existant notification id", action.id);
37 | }
38 | return Object.assign({}, state, {
39 | notifications: newNotifications,
40 | });
41 | default:
42 | return state || initialAppState;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/public/js/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import { default as app } from './app';
4 | import { default as management } from './management';
5 | import { default as processing } from './processing';
6 | import { default as transaction } from './transaction';
7 | import { default as user } from './user';
8 |
9 |
10 | const rootReducer = combineReducers({
11 | app,
12 | management,
13 | processing,
14 | transaction,
15 | user,
16 | });
17 |
18 | export default rootReducer;
19 |
--------------------------------------------------------------------------------
/public/js/reducers/processing.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 |
3 | // Map of things that are currently being processed.
4 | // Each key is a custom processing ID. When a value is `true` it means
5 | // the action is in process.
6 | //
7 | // Example:
8 | // `state['CardForm-id'] === true` would mean the CardForm is currently
9 | // being processed.
10 | // `!state['CardForm-id']` would mean the CardForm is not being processed.
11 | //
12 | export const initialProcessingState = {};
13 |
14 |
15 | export default function app(state, action) {
16 | switch (action.type) {
17 | case actionTypes.BEGIN_PROCESSING:
18 | var currentlyInProcess = Object.assign({}, state);
19 | currentlyInProcess[action.processingId] = true;
20 | return currentlyInProcess;
21 | case actionTypes.STOP_PROCESSING:
22 | var currentlyInProcess = Object.assign({}, state);
23 | if (currentlyInProcess[action.processingId]) {
24 | // To prevent old entries from hanging around,
25 | // delete the flag entirely.
26 | delete currentlyInProcess[action.processingId];
27 | }
28 | return currentlyInProcess;
29 | default:
30 | return state || initialProcessingState;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/public/js/reducers/transaction.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 |
3 |
4 | export const initialTransState = {
5 | completed: false,
6 | availablePayMethods: [],
7 | cardSubmissionErrors: null,
8 | userEmail: undefined,
9 | };
10 |
11 |
12 | export default function transaction(state, action) {
13 |
14 | if (action.type === actionTypes.COMPLETE_TRANSACTION) {
15 | return Object.assign({}, initialTransState, {
16 | completed: true,
17 | userEmail: action.userEmail,
18 | });
19 | }
20 |
21 | if (action.type === actionTypes.PAY_WITH_NEW_CARD) {
22 | // What this does is tell Transaction that there are no available
23 | // payment methods for the transaction even though the user
24 | // itself may have some. This causes a 'pay with new card' form
25 | // to appear.
26 | return Object.assign({}, initialTransState, {
27 | availablePayMethods: [],
28 | });
29 | }
30 |
31 | if (action.type === actionTypes.USER_SIGNED_IN) {
32 | // By default, assume the user wants to pay with their saved pay methods.
33 | return Object.assign({}, initialTransState, {
34 | availablePayMethods: action.user.payMethods,
35 | });
36 | }
37 |
38 | if (action.type === actionTypes.CREDIT_CARD_SUBMISSION_ERRORS) {
39 | return Object.assign({}, initialTransState, {
40 | cardSubmissionErrors: action.apiErrorResult,
41 | });
42 | }
43 |
44 | return state || initialTransState;
45 | }
46 |
--------------------------------------------------------------------------------
/public/js/reducers/user.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 |
3 |
4 | export const initialUserState = {
5 | signedIn: false,
6 | email: null,
7 | payMethods: [],
8 | braintreeToken: null,
9 | subscriptions: null,
10 | transactions: null,
11 | };
12 |
13 |
14 | export default function user(state, action) {
15 |
16 | if (action.type === actionTypes.USER_SIGNED_OUT) {
17 | return Object.assign({}, initialUserState, {
18 | signedIn: false,
19 | });
20 | }
21 |
22 | if (action.type === actionTypes.LOADING_USER_TRANSACTIONS) {
23 | return Object.assign({}, initialUserState, state, {
24 | transactions: initialUserState.transactions,
25 | });
26 | }
27 |
28 | if (action.type === actionTypes.GOT_USER_TRANSACTIONS) {
29 | return Object.assign({}, initialUserState, state, {
30 | transactions: action.transactions,
31 | });
32 | }
33 |
34 | if (action.type === actionTypes.USER_SIGNED_IN) {
35 | return Object.assign({}, initialUserState, {
36 | signedIn: true,
37 | email: action.user.email,
38 | payMethods: action.user.payMethods,
39 | });
40 | }
41 |
42 | if (action.type === actionTypes.GOT_BRAINTREE_TOKEN) {
43 | return Object.assign({}, initialUserState, state, {
44 | braintreeToken: action.braintreeToken,
45 | });
46 | }
47 |
48 | if (action.type === actionTypes.LOADING_USER_SUBS) {
49 | return Object.assign({}, initialUserState, state, {
50 | subscriptions: initialUserState.subscriptions,
51 | });
52 | }
53 |
54 | if (action.type === actionTypes.GOT_USER_SUBS) {
55 | return Object.assign({}, initialUserState, state, {
56 | subscriptions: action.subscriptions,
57 | });
58 | }
59 |
60 | if (action.type === actionTypes.GOT_PAY_METHODS) {
61 | return Object.assign({}, initialUserState, state, {
62 | payMethods: action.payMethods,
63 | });
64 | }
65 |
66 | if (action.type === actionTypes.ADD_PAY_METHOD) {
67 | return Object.assign({}, initialUserState, state, {
68 | payMethods: action.payMethods,
69 | });
70 | }
71 |
72 | if (action.type === actionTypes.DEL_PAY_METHOD) {
73 | return Object.assign({}, initialUserState, state, {
74 | payMethods: action.payMethods,
75 | });
76 | }
77 |
78 | return state || initialUserState;
79 | }
80 |
--------------------------------------------------------------------------------
/public/js/settings.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | supportedLanguages: [
3 | 'ca',
4 | 'cs',
5 | 'cy',
6 | 'da',
7 | 'de',
8 | 'dsb',
9 | 'en',
10 | 'es',
11 | 'es-AR',
12 | 'es-CL',
13 | 'et',
14 | 'eu',
15 | 'ff',
16 | 'fr',
17 | 'fy',
18 | 'he',
19 | 'hsb',
20 | 'hu',
21 | 'id',
22 | 'it',
23 | 'ja',
24 | 'ko',
25 | 'lt',
26 | 'nb-NO',
27 | 'nl',
28 | 'pa',
29 | 'pl',
30 | 'pt',
31 | 'pt-BR',
32 | 'rm',
33 | 'ru',
34 | 'sk',
35 | 'sl',
36 | 'sq',
37 | 'sr',
38 | 'sr-LATN',
39 | 'sv',
40 | 'sv-SE',
41 | 'tr',
42 | 'uk',
43 | 'zh-CN',
44 | 'zh-TW',
45 | ],
46 | tracking: {
47 | enabled: false,
48 | id: 'UA-35433268-60',
49 | },
50 |
51 | // FxA client ID (configured per host).
52 | // This is used for user sign-in, not token sign in.
53 | // The payments-service API must be configured with each
54 | // corresponding secret.
55 | fxaClientId: undefined,
56 | fxaRedirectUri: undefined,
57 | fxaContentHost: 'https://stable.dev.lcip.org',
58 | fxaOauthHost: 'https://oauth-stable.dev.lcip.org/v1',
59 | apiPrefix: '/api',
60 | allowCORSRequests: false,
61 |
62 | // This is a map of custom settings that will be defined
63 | // based on host.
64 | hostSettings: {
65 | 'pay.dev.mozaws.net:8000': {
66 | fxaClientId: '90f432a069a26c77',
67 | fxaRedirectUri: 'http://pay.dev.mozaws.net:8000',
68 | },
69 | // These are only used locally for docker.
70 | 'pay.dev:8000': {
71 | fxaClientId: '8d7c6c8549cc6deb',
72 | fxaRedirectUri: 'http://pay.dev:8000',
73 | },
74 | 'pay.webpack:8080': {
75 | fxaClientId: 'a63657a4c78dd650',
76 | fxaRedirectUri: 'http://pay.webpack:8080',
77 | // Webpack needs to talk via CORS to the dev API.
78 | // See payments-service for how this is allowed.
79 | apiPrefix: 'http://pay.dev:8000/api',
80 | allowCORSRequests: true,
81 | },
82 | },
83 | };
84 |
85 |
86 | if (typeof window !== 'undefined') {
87 | var host = window.location.host;
88 | var hostSettings = module.exports.hostSettings[host];
89 |
90 | if (!hostSettings) {
91 | // For example, the unit tests are served by localhost which is unmapped.
92 | console.warn('no custom settings for host ' + host);
93 | } else {
94 | console.log('applying custom settings for host ' + host);
95 | for (var attr in hostSettings) {
96 | module.exports[attr] = hostSettings[attr];
97 | }
98 | }
99 |
100 | } else {
101 | console.warn('probably not in a web browser; skipping host configuration');
102 | }
103 |
--------------------------------------------------------------------------------
/public/js/views/management/add-pay-method.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | import CardForm from 'components/card-form';
4 |
5 | import { default as tracking } from 'tracking';
6 | import { gettext, setTitle } from 'utils';
7 |
8 |
9 | export default class AddPayMethod extends Component {
10 |
11 | static propTypes = {
12 | addCreditCard: PropTypes.func.isRequired,
13 | braintreeToken: PropTypes.string.isRequired,
14 | cardSubmissionErrors: PropTypes.object,
15 | closeModal: PropTypes.func.isRequired,
16 | showPayMethods: PropTypes.func.isRequired,
17 | }
18 |
19 | componentDidMount() {
20 | setTitle(gettext('Add Payment Method'));
21 | tracking.setPage('/add-pay-method');
22 | }
23 |
24 | handleCardSubmit(creditCard, processingId) {
25 | console.log('submitting credit card as new pay method');
26 | this.props.addCreditCard({braintreeToken: this.props.braintreeToken,
27 | creditCard: creditCard,
28 | processingId: processingId});
29 | }
30 |
31 | render() {
32 | return (
33 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/public/js/views/management/del-pay-method.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | import PayMethodChoice from 'components/pay-method-choice';
4 |
5 | import { default as tracking } from 'tracking';
6 | import { gettext, setTitle } from 'utils';
7 |
8 |
9 | export default class DelPayMethod extends Component {
10 |
11 | static propTypes = {
12 | closeModal: PropTypes.func.isRequired,
13 | payMethods: PropTypes.array.isRequired,
14 | showConfirmDelPayMethod: PropTypes.func.isRequired,
15 | showPayMethods: PropTypes.func.isRequired,
16 | }
17 |
18 | componentDidMount() {
19 | setTitle(gettext('Delete Payment Method'));
20 | tracking.setPage('/del-pay-method');
21 | }
22 |
23 | handleSubmit = (payMethod) => {
24 | this.props.showConfirmDelPayMethod(payMethod);
25 | }
26 |
27 | render() {
28 | return (
29 |
30 |
{gettext('Delete Payment Method')}
31 |
{gettext('Choose a payment method to delete. ' +
32 | "You'll confirm on the next step.")}
33 |
40 |
41 | {gettext('Back')}
42 |
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/js/views/management/history.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import JsonTable from 'react-json-table';
3 | import fecha from 'fecha';
4 |
5 | import { configGetText, gettext, setTitle } from 'utils';
6 | import * as products from 'products';
7 | import Spinner from 'components/spinner';
8 |
9 |
10 | export default class History extends Component {
11 |
12 | static propTypes = {
13 | getUserTransactions: PropTypes.func.isRequired,
14 | transactions: PropTypes.array.isRequired,
15 | };
16 |
17 | componentDidMount() {
18 | setTitle(gettext('Transaction Receipts & History'));
19 | this.props.getUserTransactions();
20 | }
21 |
22 | showReceipt(e, transactionId) {
23 | e.preventDefault();
24 | // https://github.com/mozilla/payments-ui/issues/304
25 | console.log('TODO: show receipt for transaction', transactionId);
26 | }
27 |
28 | renderContent() {
29 |
30 | var columns = [
31 | {
32 | key: 'created',
33 | label: gettext('Date'),
34 | cell: (item, columnKey) => (
35 | fecha.format(fecha.parse(item[columnKey], 'YYYY-MM-DDThh:mm:ss.SSS'),
36 | 'MMM D, YYYY')
37 | ),
38 | },
39 | {
40 | key: 'seller',
41 | label: gettext('Vendor & Product'),
42 | cell: (item) => (
43 | configGetText(
44 | products.get(
45 | item.transaction.seller_product.public_id
46 | ).seller.name)
47 | ),
48 | },
49 | {
50 | key: 'amount',
51 | label: gettext('Amount'),
52 | cell: (item) => (
53 | // TODO: display currency with symbol (when possible)
54 | // https://github.com/mozilla/payments-ui/issues/308
55 | this.showReceipt(e, item.resource_pk)}
56 | href="#">{item.transaction.currency} {item.transaction.amount}
57 | ),
58 | },
59 | ];
60 |
61 | // TODO: this is currently showing all transactions but that might be
62 | // weird when it includes kinds other than subscription_charged_successfully
63 |
64 | if (this.props.transactions === null) {
65 | return ;
66 | } else {
67 | if (this.props.transactions.length) {
68 | return ;
69 | } else {
70 | return {gettext("You haven't purchased anything yet.")}
;
71 | }
72 | }
73 | }
74 |
75 | render() {
76 | return (
77 |
78 |
{gettext('Transaction Receipts & History')}
79 | {this.renderContent()}
80 |
81 | );
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/public/js/views/management/my-account.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { gettext, setTitle } from 'utils';
3 |
4 |
5 | export default class MyAccount extends Component {
6 |
7 | static propTypes = {
8 | user: PropTypes.object.isRequired,
9 | }
10 |
11 | componentDidMount() {
12 | setTitle(gettext('My Account'));
13 | }
14 |
15 | render() {
16 | return (
17 |
18 |
{gettext('My Account')}
19 |
20 |
{gettext('Edit My Account')}
21 |
30 |
31 |
{gettext('Delete Account')}
32 |
{gettext('When you delete your account, all of your ' +
33 | 'subscriptions will be cancelled. This action cannot ' +
34 | 'be undone. Are you sure you want to delete your account?')}
35 |
36 |
37 |
{gettext('Delete account')}
38 |
39 |
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/js/views/management/pay-methods.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import cx from 'classnames';
3 |
4 | import PayMethodList from 'components/pay-method-list';
5 |
6 | import { gettext, isDisabled, setTitle } from 'utils';
7 |
8 |
9 | export default class PayMethods extends Component {
10 |
11 | static propTypes = {
12 | getPayMethods: PropTypes.func.isRequired,
13 | payMethods: PropTypes.array.isRequired,
14 | showAddPayMethod: PropTypes.func.isRequired,
15 | showDelPayMethod: PropTypes.func.isRequired,
16 | };
17 |
18 | componentDidMount() {
19 | setTitle(gettext('Payment Methods'));
20 | this.props.getPayMethods();
21 | }
22 |
23 | handleAddPayMethod = e => {
24 | e.preventDefault();
25 | this.props.showAddPayMethod();
26 | }
27 |
28 | handleDelPayMethod = e => {
29 | e.preventDefault();
30 | if (isDisabled(e.target)) {
31 | console.log('Delete link is disabled. no-op');
32 | return;
33 | }
34 | this.props.showDelPayMethod();
35 | }
36 |
37 | renderChild() {
38 | if (this.props.payMethods && this.props.payMethods.length) {
39 | return (
40 |
41 | );
42 | }
43 | return (
44 | {gettext("You haven't added any credit cards yet")}
45 |
);
46 | }
47 |
48 | render() {
49 |
50 | var isDeleteDisabled = !this.props.payMethods ||
51 | !this.props.payMethods.length;
52 | var deleteClasses = cx('delete', {disabled: isDeleteDisabled});
53 |
54 | return (
55 |
56 |
{gettext('Payment Methods')}
57 |
58 | {this.renderChild()}
59 |
69 |
70 |
71 | );
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/public/js/views/management/subscriptions.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import SubscriptionList from 'components/subscription-list';
3 |
4 | import { gettext, setTitle } from 'utils';
5 |
6 |
7 | export default class Subscriptions extends Component {
8 |
9 | static propTypes = {
10 | getUserSubscriptions: PropTypes.func.isRequired,
11 | payMethods: PropTypes.array.isRequired,
12 | userSubscriptions: PropTypes.array.isRequired,
13 | };
14 |
15 | componentDidMount() {
16 | this.props.getUserSubscriptions();
17 | setTitle(gettext('Subscriptions'));
18 | }
19 |
20 | render() {
21 |
22 | return (
23 |
24 |
{gettext('Subscriptions')}
25 |
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/public/js/views/shared/braintree-token.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | import Spinner from 'components/spinner';
4 |
5 | import { gettext } from 'utils';
6 | import tracking from 'tracking';
7 |
8 | export default class BrainTree extends Component {
9 |
10 | static propTypes = {
11 | getBraintreeToken: PropTypes.func.isRequired,
12 | };
13 |
14 | componentDidMount() {
15 | tracking.setPage('/braintree-token');
16 | this.props.getBraintreeToken();
17 | }
18 |
19 | render() {
20 | return ;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/public/js/views/shared/sign-in.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | import Spinner from 'components/spinner';
4 |
5 | import { gettext } from 'utils';
6 | import tracking from 'tracking';
7 |
8 |
9 | export default class SignIn extends Component {
10 |
11 | static propTypes = {
12 | accessToken: PropTypes.string.isRequired,
13 | allowUserSignIn: PropTypes.bool,
14 | error: PropTypes.func.isRequired,
15 | tokenSignIn: PropTypes.func.isRequired,
16 | user: PropTypes.object.isRequired,
17 | userSignIn: PropTypes.func.isRequired,
18 | };
19 |
20 | static defaultProps = {
21 | allowUserSignIn: true,
22 | }
23 |
24 | componentDidMount() {
25 | tracking.setPage('/sign-in');
26 | if (!this.props.user.signedIn) {
27 | if (this.props.accessToken) {
28 | console.log('signing in with access token');
29 | this.props.tokenSignIn(this.props.accessToken);
30 | } else if (this.props.allowUserSignIn) {
31 | console.log('prompting user to sign in');
32 | this.props.userSignIn();
33 | } else {
34 | return this.props.error(
35 | 'cannot sign in: no access token provided'
36 | );
37 | }
38 | }
39 | }
40 |
41 | render() {
42 | return ;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/public/js/views/shared/sign-out.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | import Spinner from 'components/spinner';
4 |
5 | import { gettext } from 'utils';
6 | import tracking from 'tracking';
7 |
8 |
9 | export default class SignOut extends Component {
10 |
11 | static propTypes = {
12 | showSignIn: PropTypes.func.isRequired,
13 | user: PropTypes.object.isRequired,
14 | userSignOut: PropTypes.func.isRequired,
15 | };
16 |
17 | componentDidMount() {
18 | tracking.setPage('/sign-out');
19 | if (this.props.user.signedIn) {
20 | this.props.userSignOut();
21 | }
22 | }
23 |
24 | handleShowSignIn = e => {
25 | e.preventDefault();
26 | this.props.showSignIn();
27 | }
28 |
29 | render() {
30 | if (this.props.user.signedIn) {
31 | return ;
32 | } else {
33 | return (
34 |
35 |
36 | {gettext('Sign In')}
38 |
39 |
{gettext('Sign in to add and remove payment methods, ' +
40 | 'view receipts, and manage subscriptions.')}
41 |
42 | );
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/public/js/views/transaction/product-pay-chooser.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | import PayMethodChoice from 'components/pay-method-choice';
4 | import ProductDetail from 'components/product-detail';
5 |
6 | import * as products from 'products';
7 | import { gettext } from 'utils';
8 | import tracking from 'tracking';
9 |
10 |
11 | export default class ProductPayChooser extends Component {
12 |
13 | static propTypes = {
14 | payMethods: PropTypes.array.isRequired,
15 | payWithNewCard: PropTypes.func.isRequired,
16 | processPayment: PropTypes.func.isRequired,
17 | productId: PropTypes.string.isRequired,
18 | userDefinedAmount: PropTypes.string,
19 | }
20 |
21 | componentDidMount() {
22 | tracking.setPage('/product-pay-chooser');
23 | }
24 |
25 | handleSubmit = (payMethodUri, processingId) => {
26 | this.props.processPayment({productId: this.props.productId,
27 | userDefinedAmount: this.props.userDefinedAmount,
28 | payMethodUri: payMethodUri,
29 | processingId: processingId});
30 | }
31 |
32 | render() {
33 | var product = products.get(this.props.productId);
34 | var submitPrompt;
35 | if (product.seller.kind === 'donations') {
36 | submitPrompt = gettext('Donate now');
37 | } else {
38 | // TODO: also handle non-recurring, non-donations here.
39 | submitPrompt = gettext('Subscribe');
40 | }
41 |
42 | return (
43 |
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/public/js/views/transaction/product-pay.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | import CardForm from 'components/card-form';
4 | import ProductDetail from 'components/product-detail';
5 |
6 | import * as products from 'products';
7 | import { default as tracking } from 'tracking';
8 | import { gettext } from 'utils';
9 |
10 |
11 | export default class ProductPay extends Component {
12 |
13 | static propTypes = {
14 | braintreeToken: PropTypes.string.isRequired,
15 | cardSubmissionErrors: PropTypes.object,
16 | processPayment: PropTypes.func.isRequired,
17 | productId: PropTypes.string.isRequired,
18 | userDefinedAmount: PropTypes.string,
19 | }
20 |
21 | static defaultProps = {
22 | cardSubmissionErrors: null,
23 | }
24 |
25 | componentDidMount() {
26 | tracking.setPage('/product-pay');
27 | }
28 |
29 | handleCardSubmit = (creditCard, processingId, {email} = {}) => {
30 | console.log('submitting credit card to sign up for subscription',
31 | this.props.productId);
32 | var data = {
33 | processingId: processingId,
34 | productId: this.props.productId,
35 | creditCard: creditCard,
36 | braintreeToken: this.props.braintreeToken,
37 | userDefinedAmount: this.props.userDefinedAmount,
38 | };
39 | if (email) {
40 | console.log('Card submission included an email address');
41 | data.email = email;
42 | }
43 | this.props.processPayment(data);
44 | }
45 |
46 | render() {
47 | var product = products.get(this.props.productId);
48 | var submitPrompt;
49 | if (product.seller.kind === 'donations') {
50 | submitPrompt = gettext('Donate now');
51 | var emailFieldRequired = product.user_identification === 'email';
52 | } else {
53 | // TODO: also handle non-recurring, non-donations here.
54 | submitPrompt = gettext('Subscribe');
55 | }
56 |
57 | return (
58 |
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/public/management.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Management
10 |
11 |
12 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/public/media/README.md:
--------------------------------------------------------------------------------
1 | Only used for tabzilla.
2 |
--------------------------------------------------------------------------------
/public/media/img/tabzilla-static-high-res.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/media/img/tabzilla-static-high-res.png
--------------------------------------------------------------------------------
/public/media/img/tabzilla-static.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/public/media/img/tabzilla-static.png
--------------------------------------------------------------------------------
/public/scss/_base.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | height: 100%;
6 | }
7 |
8 | main {
9 | padding: 20px;
10 | width: 100%;
11 | }
12 |
13 | hr {
14 | border: 1px solid $heading-border;
15 | border-bottom: none;
16 | border-right: none;
17 | border-left: none;
18 | margin: 2em 0;
19 | }
20 |
--------------------------------------------------------------------------------
/public/scss/_buttons.scss:
--------------------------------------------------------------------------------
1 | .button {
2 | display: inline-block;
3 | text-decoration: none;
4 | }
5 |
6 | .button,
7 | a.button,
8 | button {
9 | @include font();
10 | background: $button-background-color;
11 | border: 0;
12 | border-radius: $small-border-radius;
13 | color: $message-text-color;
14 | cursor: pointer;
15 | font-size: $medium-font;
16 | margin: 10px 0;
17 | padding: 0.5em 1em;
18 | transition-duration: $short-transition;
19 | transition-property: background-color;
20 | text-align: center;
21 | width: 100%;
22 | line-height: 1.5em;
23 |
24 | &:active,
25 | &:hover,
26 | &:focus {
27 | background: $button-background-hover-color;
28 | }
29 |
30 | &:focus {
31 | @include focus();
32 | }
33 |
34 | &:disabled,
35 | &.disabled {
36 | background: $button-background-disabled-color;
37 | cursor: not-allowed;
38 | }
39 |
40 | &.warning {
41 | background: $awooga-red;
42 | }
43 | }
44 |
45 | button::-moz-focus-inner {
46 | border: 0;
47 | padding: 0;
48 | }
49 |
--------------------------------------------------------------------------------
/public/scss/_complete-payment.scss:
--------------------------------------------------------------------------------
1 | .complete {
2 | text-align: center;
3 |
4 | .accepted {
5 | @include header-font();
6 | font-size: $large-font;
7 | font-weight: 200;
8 | padding: 20px 0;
9 | }
10 |
11 | .receipt {
12 | padding-bottom: 1em;
13 |
14 | span {
15 | display: block;
16 | }
17 | }
18 |
19 | button {
20 | max-width: calc(100% - 40px);
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/public/scss/_global.scss:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
--------------------------------------------------------------------------------
/public/scss/_json-table.scss:
--------------------------------------------------------------------------------
1 | .jsonTable {
2 | width: 100%;
3 |
4 | td, th {
5 | text-align: left;
6 | padding: 0.35em;
7 | }
8 |
9 | thead {
10 | border: 1px solid $ship-grey;
11 | }
12 |
13 | th {
14 | font-weight: normal;
15 | background-color: $lightish-grey;
16 | color: $moody-grey;
17 |
18 | select {
19 | background-color: $lightish-grey;
20 | border-radius: 2px;
21 | border: 1px solid $ship-grey;
22 | color: $moody-grey;
23 | padding: 0.3em;
24 | }
25 | }
26 |
27 | tr {
28 | border-left: 1px solid $ship-grey;
29 | border-right: 1px solid $ship-grey;
30 |
31 | &.jsonEven {
32 | background-color: $management-grey;
33 | }
34 |
35 | &:last-child {
36 | border-bottom: 1px solid $ship-grey;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/scss/_mgmt-nav-sprite.scss:
--------------------------------------------------------------------------------
1 | .nav:after {
2 | background: url(../img/mgmt-nav-sprite.svg) no-repeat;
3 | }
4 |
5 | .history:after {
6 | background-position: 0 0;
7 | width: 21.84px;
8 | height: 28px;
9 | }
10 |
11 | .pay-methods:after {
12 | background-position: 0 33.333333333333336%;
13 | width: 28px;
14 | height: 28px;
15 | }
16 |
17 | .profile:after {
18 | background-position: 0 66.66666666666667%;
19 | width: 28px;
20 | height: 28px;
21 | }
22 |
23 | .subs:after {
24 | background-position: 0 100%;
25 | width: 24.29px;
26 | height: 28px;
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/public/scss/_modal.scss:
--------------------------------------------------------------------------------
1 | .modal {
2 |
3 | &.active {
4 | opacity: 1;
5 | }
6 |
7 | background: rgba(0, 0, 0, 0.5);
8 | bottom: 0;
9 | height: 100%;
10 | left: 0;
11 | opacity: 0;
12 | position: fixed;
13 | right: 0;
14 | top: 0;
15 | // Ensure transform on .inner doesn't
16 | // render on half a pixel thus causing fuzziness.
17 | transform-style: preserve-3d;
18 | z-index: 20;
19 |
20 | .inner {
21 | background: #fff;
22 | border-radius: 3px;
23 | border: 1px solid #C3CFD8;
24 | box-shadow: 0 3px 7px rgba(0, 0, 0, 0.5);
25 | height: 468px;
26 | margin: 0 auto;
27 | padding: 20px;
28 | position: relative;
29 | top: 50%;
30 | transform: translateY(-50%);
31 | width: 318px;
32 | }
33 |
34 | .close {
35 | color: #666;
36 | font-size: $large-font;
37 | line-height: 1ex;
38 | padding: 10px;
39 | position: absolute;
40 | right: -5px;
41 | text-decoration: none;
42 | top: -5px;
43 | z-index: 30;
44 |
45 | &:before {
46 | content: '×';
47 | }
48 | }
49 |
50 | header {
51 | text-align: center;
52 |
53 | h2 {
54 | margin: 1em;
55 | font-weight: 400;
56 | }
57 | }
58 |
59 | form {
60 | padding-top: 0;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/public/scss/_notification.scss:
--------------------------------------------------------------------------------
1 | .notification-list {
2 | color: #fff;
3 | left: 200px;
4 | margin: 0;
5 | padding: 0;
6 | position: fixed;
7 | right: 0;
8 | text-align: center;
9 | top: 0;
10 | z-index: 30;
11 |
12 | .transaction & {
13 | position: static;
14 | left: 20px;
15 | right: 20px;
16 | margin-bottom: 20px;
17 | }
18 | }
19 |
20 |
21 |
22 | .notification {
23 | list-style-type: none;
24 | margin: 0;
25 | padding: 0.5em;
26 | position: relative;
27 |
28 | .transaction & {
29 | border-radius: 3px;
30 | }
31 |
32 | &.error {
33 | background: transparentize($awooga-red, 0.1);
34 | }
35 |
36 | &.info {
37 | background: transparentize($light-blue, 0.1);
38 | }
39 |
40 | .dismiss:after {
41 | color: #fff;
42 | content: '×';
43 | display: block;
44 | height: 2em;
45 | position: absolute;
46 | right: 10px;
47 | top: 50%;
48 | transform: translateY(-50%);
49 | width: 2em;
50 | }
51 | }
52 |
53 | .notification-enter {
54 | opacity: 0.01;
55 |
56 | &.notification-enter-active {
57 | opacity: 1;
58 | transition: opacity .5s ease-in;
59 | }
60 | }
61 |
62 | .notification-leave {
63 | opacity: 1;
64 |
65 | &.notification-leave-active {
66 | opacity: 0.01;
67 | transition: opacity .5s ease-in;
68 | }
69 | }
70 |
71 | .notification-appear {
72 | opacity: 0.01;
73 | transition: opacity .5s ease-in;
74 |
75 | &.notification-appear-active {
76 | opacity: 1;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/public/scss/_pay-method-drop-down.scss:
--------------------------------------------------------------------------------
1 | .proxy-select {
2 |
3 | background: #fff;
4 | border: 1px solid #ccc;
5 | border-radius: 4px;
6 | font-size: $medium-font;
7 | margin: 1em 0;
8 | padding: 0.5em 1.35em 0.5em 0.5em;
9 | position: relative;
10 | width: 100%;
11 | max-width: 300px;
12 |
13 | &.active {
14 | border-color: $default-focus-color;
15 | box-shadow: 0 0 1px 0 $default-focus-color;
16 | }
17 |
18 | .has-pay-method {
19 | padding-left: 50px;
20 | }
21 |
22 | .pay-method-icon {
23 | left: 8px;
24 | }
25 |
26 | &:after {
27 | font-size: 12px;
28 | color: #666;
29 | content: '\25BC';
30 | display: block;
31 | right: 0.5em;
32 | top: 50%;
33 | transform: translateY(-50%);
34 | position: absolute;
35 | }
36 |
37 | select {
38 | -moz-appearance: none;
39 | -webkit-appearance: none;
40 | appearance: none;
41 | opacity: 0;
42 | position: absolute;
43 | top: 0;
44 | right: 0;
45 | left: 0;
46 | bottom: 0;
47 | width: 100%;
48 | height: 100%;
49 | z-index: 10;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/public/scss/_pay-method-item.scss:
--------------------------------------------------------------------------------
1 | .pay-method-item {
2 | position: relative;
3 |
4 | input[type=hidden] + label {
5 | padding: 1em 0 1em 70px;
6 | text-align: left;
7 | }
8 |
9 | input[type=radio] {
10 | opacity: 0;
11 | position: absolute;
12 |
13 | &:checked + label,
14 | &:hover + label,
15 | &:focus + label {
16 | cursor: pointer;
17 | border: 1px solid $radio-selected-border;
18 | border-radius: 5px;
19 | background: $radio-selected-background;
20 | transition: all $medium-transition;
21 | }
22 | }
23 |
24 | & label {
25 | border: 1px solid transparent;
26 | display: block;
27 | font-size: $medium-font;
28 | font-weight: 700;
29 | padding: 1em 15px 1em 60px;
30 | position: relative;
31 | text-align: right;
32 | width: 100%;
33 | }
34 |
35 | .pay-method-icon {
36 | left: 15px;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/public/scss/_pay-method-list.scss:
--------------------------------------------------------------------------------
1 | .pay-method-list {
2 | margin: 0;
3 | padding: 0;
4 |
5 | li {
6 | list-style-type: none;
7 | margin: 10px 0;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/public/scss/_pay-method-sprite.scss:
--------------------------------------------------------------------------------
1 | .pay-method-icon {
2 | background: url(../img/pay-method-sprite.svg) no-repeat;
3 | }
4 |
5 | .pmtype-amex {
6 | background-position: 0 0;
7 | width: 40px;
8 | height: 25.05px;
9 | }
10 |
11 | .pmtype-diners {
12 | background-position: 0 16.57317695053544%;
13 | width: 40px;
14 | height: 25.12px;
15 | }
16 |
17 | .pmtype-discover {
18 | background-position: 0 33.26722538545199%;
19 | width: 40px;
20 | height: 25.69px;
21 | }
22 |
23 | .pmtype-jcb {
24 | background-position: 0 49.71953085160632%;
25 | width: 40px;
26 | height: 25.12px;
27 | }
28 |
29 | .pmtype-maestro {
30 | background-position: 0 66.29270780214176%;
31 | width: 40px;
32 | height: 25.12px;
33 | }
34 |
35 | .pmtype-mastercard {
36 | background-position: 0 82.8658847526772%;
37 | width: 40px;
38 | height: 25.12px;
39 | }
40 |
41 | .pmtype-visa {
42 | background-position: 0 99.43906170321264%;
43 | width: 40px;
44 | height: 25.12px;
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/public/scss/_pay-methods.scss:
--------------------------------------------------------------------------------
1 | .no-results {
2 | padding: 2em 0;
3 | }
4 |
5 | .button.add-pay-method {
6 | margin-right: 1em;
7 | }
8 |
--------------------------------------------------------------------------------
/public/scss/_product.scss:
--------------------------------------------------------------------------------
1 | .product {
2 | display: block;
3 | text-align: center;
4 | @include header-font();
5 |
6 | .title {
7 | font-size: $medium-font;
8 | font-weight: 400;
9 | padding-bottom: 0.35em;
10 | margin: 0;
11 | }
12 |
13 | .price {
14 | font-size: $very-large-font;
15 | line-height: 1em;
16 | font-weight: 200;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/public/scss/_signed-out.scss:
--------------------------------------------------------------------------------
1 | .management .signed-out {
2 | width: 100%;
3 | margin-top: 26%;
4 |
5 | p {
6 | width: 220px;
7 | margin: 0 auto 1em auto;
8 | }
9 |
10 | .button.quiet {
11 | background-color: transparent;
12 | width: 100%;
13 | margin-bottom: 0;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/public/scss/_spinner.scss:
--------------------------------------------------------------------------------
1 |
2 | //used to spin loader elements
3 | @keyframes spin {
4 | from {
5 | transform: rotate(0deg);
6 | }
7 | to {
8 | transform: rotate(360deg);
9 | }
10 | }
11 |
12 | .spinner-cont {
13 | margin: 0 auto;
14 | max-width: 100%;
15 | width: 300px;
16 | .text {
17 | display: block;
18 | margin: 0 auto;
19 | padding: 10px;
20 | text-align: center;
21 | }
22 | }
23 |
24 | .spinner:after {
25 | margin: 130px auto 0;
26 | content: '';
27 | animation: 0.9s spin infinite linear;
28 | display: block;
29 | overflow: hidden;
30 | text-indent: 100%;
31 | font-size: 0;
32 | white-space: nowrap;
33 | @include hidpi-background-image('spinnerlight', 36px 36px);
34 | height: 36px;
35 | width: 36px;
36 | }
37 |
38 | button.spinner {
39 | position: relative;
40 | text-indent: -999em;
41 | direction: ltr;
42 |
43 | &:after {
44 | @include hidpi-background-image('spinnerwhite', 30px 30px);
45 | content: '';
46 | display: block;
47 | height: 30px;
48 | left: 0;
49 | margin: -15px auto 0;
50 | position: absolute;
51 | right: 0;
52 | top: 50%;
53 | width: 30px;
54 | z-index: 10;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/public/scss/_subscriptions.scss:
--------------------------------------------------------------------------------
1 | .subscription-list {
2 | margin: 1em 0;
3 | padding: 0;
4 |
5 | li {
6 | list-style-type: none;
7 | padding: 2em 0 0;
8 | margin: 2em 0 0;
9 | border-top: 1px solid $heading-border;
10 |
11 | &:first-child {
12 | border-top: none;
13 | }
14 | }
15 |
16 | &.compact {
17 | margin-bottom: 2em;
18 |
19 | li {
20 | border-top: none;
21 | margin: 1em 0 0;
22 | padding: 1em 0 0;
23 | }
24 |
25 | .product {
26 | max-width: 100%;
27 |
28 | .description {
29 | display: inline;
30 | }
31 |
32 | .price {
33 | border-top: none;
34 | display: inline;
35 |
36 | &:before {
37 | content: ' – ';
38 | }
39 | }
40 | }
41 | }
42 | }
43 |
44 |
45 |
46 |
47 | .subscription {
48 | overflow: hidden;
49 |
50 | .proxy-select {
51 | margin-top: 0;
52 | }
53 |
54 | .product {
55 | max-width: 50%;
56 |
57 | img {
58 | display: block;
59 | float: left;
60 | margin-right: 20px;
61 | width: 50px;
62 | }
63 |
64 | .meta {
65 | float: left;
66 | width: calc(100% - 70px);
67 | }
68 |
69 | .seller {
70 | font-weight: normal;
71 | margin: 0 0 0.1em 0;
72 | overflow: hidden;
73 | padding: 0;
74 | text-overflow: ellipsis;
75 | white-space: nowrap;
76 | }
77 |
78 | .description {
79 | margin: 0 0 1em;
80 | text-align: left;
81 | }
82 |
83 | .next-payment,
84 | .price {
85 | border-top: 1px solid $heading-border;
86 | display: block;
87 | padding: 0.5em 0;
88 | }
89 | }
90 |
91 | .actions,
92 | .cancel,
93 | .change-pay-account {
94 | float: right;
95 | }
96 | }
97 |
98 |
99 |
--------------------------------------------------------------------------------
/public/scss/_tooltip.scss:
--------------------------------------------------------------------------------
1 | .tooltip {
2 | opacity: 1;
3 | transition: 0.1s opacity $short-transition;
4 | display: block;
5 | color: #fff;
6 | background: $error-background-color;
7 | padding: 0.5em 0.75em 0.7em;
8 | z-index: 10;
9 | position: absolute;
10 | white-space: nowrap;
11 | top: -2.1em;
12 |
13 | &:before {
14 | content: '';
15 | width: 0;
16 | height: 0;
17 | // Dashed fixes anti-aliasing in ff.
18 | border-left: 1em dashed transparent;
19 | border-right: 1em dashed transparent;
20 | border-top: 1em solid $error-background-color;
21 | border-bottom: 0;
22 | top: 100%;
23 | left: 0.5em;
24 | position: absolute;
25 | z-index: 9;
26 | }
27 |
28 | &.right {
29 | right: 0;
30 |
31 | &:before {
32 | left: inherit;
33 | right: 0.5em;
34 | text-align: right;
35 | }
36 | }
37 |
38 | &.center {
39 | left: 50%;
40 | transform: translateX(-50%);
41 | &:before {
42 | left: calc(50% - 1em);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/scss/_transaction.scss:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 30px;
3 | }
4 |
--------------------------------------------------------------------------------
/public/scss/_typography.scss:
--------------------------------------------------------------------------------
1 | // $local-name, $path, $family, $font-weight, $font-style="normal"
2 |
3 | // Fira Sans
4 | @include font-face('Fira Sans Regular', 'firasans/firasans-light', 'Fira Sans', 200);
5 | @include font-face('Fira Sans Regular Italic', 'firasans/firasans-lightitalic', 'Fira Sans', 200, 'italic');
6 |
7 | @include font-face('Fira Sans Regular', 'firasans/firasans-regular', 'Fira Sans', 400);
8 | @include font-face('Fira Sans Regular Italic', 'firasans/firasans-regularitalic', 'Fira Sans', 400, 'italic');
9 |
10 | @include font-face('Fira Sans Medium', 'firasans/firasans-medium', 'Fira Sans', 500);
11 | @include font-face('Fira Sans Medium Italic', 'firasans/firasans-mediumitalic', 'Fira Sans', 500, 'italic');
12 |
13 | @include font-face('Fira Sans Bold', 'firasans/firasans-bold', 'Fira Sans', 700);
14 | @include font-face('Fira Sans Bold Italic', 'firasans/firasans-bolditalic', 'Fira Sans', 700, 'italic');
15 |
16 | // Clear Sans
17 | @include font-face('Clear Sans Regular', 'clearsans/clearsans-regular', 'Clear Sans', 400);
18 | @include font-face('Clear Sans Italic', 'clearsans/clearsans-italic', 'Clear Sans', 400, 'italic');
19 |
20 | @include font-face('Clear Sans Medium', 'clearsans/clearsans-medium', 'Clear Sans', 500);
21 | @include font-face('Clear Sans Medium Italic', 'clearsans/clearsans-mediumitalic', 'Clear Sans', 500, 'italic');
22 |
23 | @include font-face('Clear Sans Bold', 'clearsans/clearsans-bold', 'Clear Sans', 700);
24 | @include font-face('Clear Sans Bold Italic', 'clearsans/clearsans-bolditalic', 'Clear Sans', 700, 'italic');
25 |
26 | h1,h2,h3,h4,h5,h6 {
27 | @include header-font();
28 | line-height: 1.35em;
29 | margin: .75em 0;
30 | }
31 |
32 | body {
33 | @include font();
34 | color: $text-color;
35 | font-size: $base-font;
36 | }
37 |
38 | h1,h2,h3,h4,h5,h6 {
39 | @include header-font();
40 | }
41 |
42 | a {
43 | text-decoration: none;
44 | color: $the-real-blue;
45 | }
46 |
47 | h1 { font-size: 150%; }
48 | h2 { font-size: 140%; }
49 | h3 { font-size: 130%; }
50 | h4 { font-size: 120%; }
51 | h5 { font-size: 110%; }
52 |
53 | .warn {
54 | color: $awooga-red;
55 | }
56 |
--------------------------------------------------------------------------------
/public/scss/_utils.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Hide only visually, but have it available for screen readers:
3 | * http://snook.ca/archives/html_and_css/hiding-content-for-accessibility
4 | */
5 |
6 | .vh,
7 | .visuallyhidden {
8 | @include visuallyhidden();
9 | }
10 |
--------------------------------------------------------------------------------
/public/scss/common.scss:
--------------------------------------------------------------------------------
1 | @import 'lib/normalize';
2 | @import 'inc/vars';
3 | @import 'inc/mixins';
4 | @import '_base';
5 | @import '_global';
6 | @import '_buttons';
7 | @import '_forms';
8 | @import '_notification';
9 | @import '_pay-methods';
10 | @import '_pay-method-item';
11 | @import '_pay-method-list';
12 | @import '_pay-method-drop-down';
13 | @import '_pay-method-sprite';
14 | @import '_spinner';
15 | @import '_tooltip';
16 | @import '_typography';
17 | @import '_utils';
18 |
19 |
--------------------------------------------------------------------------------
/public/scss/config/dev/_settings.scss:
--------------------------------------------------------------------------------
1 | $base-path: '/';
2 |
--------------------------------------------------------------------------------
/public/scss/config/local/_settings.scss:
--------------------------------------------------------------------------------
1 | $base-path: '../';
2 |
--------------------------------------------------------------------------------
/public/scss/email.scss:
--------------------------------------------------------------------------------
1 | @import 'inc/vars';
2 | @import 'inc/mixins';
3 |
4 | @import '_typography';
5 | @import '_utils';
6 | @import '_buttons';
7 | @import '_email';
8 |
--------------------------------------------------------------------------------
/public/scss/inc/mixins.scss:
--------------------------------------------------------------------------------
1 | @mixin font() {
2 | font-family: $default-font;
3 | }
4 |
5 | @mixin header-font() {
6 | font-family: $header-font;
7 | }
8 |
9 | @mixin respond-to($breakpoint) {
10 | @if map-has-key($breakpoints, $breakpoint) {
11 | @media #{map-get($breakpoints, $breakpoint)} {
12 | @content;
13 | }
14 | }
15 | @else {
16 | @warn 'Unfortunately, no value could be retrieved from `#{$breakpoint}`. '
17 | + 'Please make sure it is defined in `$breakpoints` map.';
18 | }
19 | }
20 |
21 | @mixin placeholder() {
22 | color: $input-placeholder-color !important;
23 | }
24 |
25 | @mixin center-text() {
26 | text-align: center !important;
27 | }
28 |
29 | @mixin focus() {
30 | border-color: $default-focus-color;
31 | box-shadow:0 0 5px 0 $default-focus-color;
32 | outline:none;
33 | }
34 |
35 | @mixin font-face($local-name, $path, $family, $font-weight, $font-style:'normal') {
36 | $filepath: "#{$font-path}#{$path}";
37 | @font-face {
38 | font-family: "#{$family}";
39 | src: url("#{$filepath}.eot");
40 | src: local($local-name),
41 | url("#{$filepath}.woff") format('woff'),
42 | url("#{$filepath}.ttf") format('truetype');
43 | font-weight: unquote($font-weight);
44 | font-style: unquote($font-style);
45 | }
46 | }
47 |
48 | // Image management
49 | @mixin hidpi-background-image($filename, $background-size: 'mixed', $extension: png) {
50 | background-image: url("#{$image-path}#{$filename}.#{$extension}");
51 | @if ($background-size != 'mixed') {
52 | background-size: $background-size;
53 | }
54 | @media (min--moz-device-pixel-ratio: 1.3),
55 | (-o-min-device-pixel-ratio: 2.6/2),
56 | (-webkit-min-device-pixel-ratio: 1.3),
57 | (min-device-pixel-ratio: 1.3),
58 | (min-resolution: 1.3dppx) {
59 | background-image: url("#{$image-path}#{$filename}@2x.#{$extension}");
60 | }
61 | }
62 |
63 | @mixin visuallyhidden() {
64 | position: absolute;
65 | overflow: hidden;
66 | clip: rect(0 0 0 0);
67 | height: 1px; width: 1px;
68 | margin: -1px; padding: 0; border: 0;
69 | }
70 |
--------------------------------------------------------------------------------
/public/scss/inc/vars.scss:
--------------------------------------------------------------------------------
1 | @import '_settings.scss';
2 |
3 | $image-path: '#{$base-path}img/';
4 | $font-path: '#{$base-path}fonts/';
5 | $svg-path: '#{$base-path}svg/';
6 |
7 | // Font Family Vars
8 | $default-font: "Clear Sans", Helvetica, Arial, sans-serif;
9 | $header-font: "Fira Sans", Helvetica, Arial, sans-serif;
10 |
11 | // Colour Vars
12 | $baby-blue: #E0F1FA;
13 | $light-blue: #0096dc;
14 | $uniform-blue: #0080D8;
15 | $the-real-blue: #0095dd;
16 | $management-grey: #F2F2F2;
17 | $subtle-grey: #F5F5F5;
18 | $stormy-grey: #BDBFBD;
19 | $moody-grey: #8A8A8A;
20 | $awooga-red: #d63920;
21 |
22 | $active-orange: #FF9500;
23 | $active-grey: #343F48;
24 | $ship-grey: #c1c1c1;
25 | $prefs-grey: #424F5A;
26 | $hover-grey: #5E6972;
27 | $dark-grey: #333;
28 | $lightish-grey: #EBEBEB;
29 | $mozlogo: #A5B1B6;
30 |
31 | $heading-border: #C8C8C8;
32 |
33 | $button-background-color: $the-real-blue;
34 | $button-background-disabled-color: #8a9ba8;
35 | $button-background-hover-color: #08c;
36 | $default-focus-color: $light-blue;
37 |
38 | $error-background-color: #d63920;
39 | $error-text-color: #d63920;
40 |
41 | $input-border-color: #424f59;
42 | $input-text-color: #424f59;
43 | $input-placeholder-color: #8a9ba8;
44 | $input-row-border-color: #8a9ba8;
45 | $input-row-focus-border-color: $the-real-blue;
46 | $input-row-hover-border-color: #424f59;
47 | $input-row-box-shadow-color: #f2f2f2;
48 | $input-left-right-padding: 20px;
49 |
50 | $radio-selected-background : $baby-blue;
51 | $radio-selected-border: $uniform-blue;
52 |
53 | $message-text-color: #fff;
54 | $mobile-html-background-color: #fff;
55 |
56 | $text-color: #424f59;
57 |
58 | // Font-Size Variables
59 | $very-large-font: 42px;
60 | $extra-large-font: 36px;
61 | $large-font: 24px;
62 | $medium-font: 18px;
63 | $base-font: 14px;
64 | $small-font: 12px;
65 | $media-adjustment: 2px;
66 |
67 |
68 | // Animation Variables
69 | $short-transition: 150ms;
70 | $medium-transition: $short-transition * 3;
71 | $long-transition: $short-transition * 7;
72 |
73 | // Breakpoints.
74 | $breakpoints: (
75 | small: '(max-width: 520px), (orientation:landscape) and (min-width:481px) and (max-height: 480px)',
76 | big: '(min-width: 521px)'
77 | );
78 |
79 | // Border-Radius Variables
80 | $small-border-radius: 4px;
81 | $big-border-radius: 4px;
82 |
--------------------------------------------------------------------------------
/public/scss/lib/tabzilla.scss:
--------------------------------------------------------------------------------
1 | #tabzilla {
2 | float: right;
3 | position: relative;
4 | }
5 | #tabzilla a {
6 | text-indent: 120%;
7 | white-space: nowrap;
8 | overflow: hidden;
9 | background-image: url('../media/img/tabzilla-static.png');
10 | background-repeat: no-repeat;
11 | display: block;
12 | height: 37px;
13 | position: relative;
14 | width: 147px;
15 | z-index: 2;
16 | }
17 | @media (-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx) {
18 | #tabzilla a {
19 | background-image: url('../media/img/tabzilla-static-high-res.png');
20 | -webkit-background-size: 147px 37px;
21 | background-size: 147px 37px;
22 | }
23 | }
24 | #tabzilla:before {
25 | background-color: transparent;
26 | content: '';
27 | display: block;
28 | height: 26px;
29 | left: 28px;
30 | position: absolute;
31 | top: 0;
32 | width: 88px;
33 | z-index: 1;
34 | }
35 |
--------------------------------------------------------------------------------
/public/scss/management.scss:
--------------------------------------------------------------------------------
1 | @import 'inc/vars';
2 | @import 'inc/mixins';
3 |
4 | @import '_modal';
5 | @import '_subscriptions';
6 | @import '_mgmt-nav-sprite.scss';
7 | @import '_json-table';
8 | @import '_signed-out';
9 | @import 'lib/tabzilla';
10 | @import '_management';
11 |
--------------------------------------------------------------------------------
/public/scss/transaction.scss:
--------------------------------------------------------------------------------
1 | @import 'inc/vars';
2 | @import 'inc/mixins';
3 |
4 | @import '_product';
5 | @import '_complete-payment';
6 | @import '_transaction';
7 |
--------------------------------------------------------------------------------
/public/svg/diners.svg:
--------------------------------------------------------------------------------
1 | diners
--------------------------------------------------------------------------------
/public/svg/discover.svg:
--------------------------------------------------------------------------------
1 | discover
--------------------------------------------------------------------------------
/public/svg/history.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/svg/jcb.svg:
--------------------------------------------------------------------------------
1 | Slice 1
--------------------------------------------------------------------------------
/public/svg/pay-methods.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/svg/profile.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/svg/subs.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/svg/visa.svg:
--------------------------------------------------------------------------------
1 | Slice 1
--------------------------------------------------------------------------------
/styleguide/jsx/card-form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CardForm } from 'components/card-form';
3 |
4 | React.render( , document.getElementById('view'));
5 |
--------------------------------------------------------------------------------
/styleguide/jsx/modal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Modal from 'components/modal';
3 | import { CardForm } from 'components/card-form';
4 |
5 | React.render(
6 |
7 | , document.getElementById('modal'));
8 |
--------------------------------------------------------------------------------
/styleguide/jsx/spinner.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Spinner from 'components/spinner';
3 |
4 | React.render( , document.getElementById('view'));
5 |
--------------------------------------------------------------------------------
/styleguide/pages/buttons.md:
--------------------------------------------------------------------------------
1 | # Buttons
2 |
3 | Here's some examples of the buttons styles used in the payments-ui.
4 |
5 | ```iframe
6 | title: Standard Buttons
7 | renderer: nunjucks
8 | template: buttons.html
9 | ```
10 |
11 | These are the spinner buttons.
12 |
13 | ```iframe
14 | title: buttons with spinner
15 | renderer: nunjucks
16 | template: buttons-spinner.html
17 | ```
18 |
--------------------------------------------------------------------------------
/styleguide/pages/card-form.md:
--------------------------------------------------------------------------------
1 | # Card Form
2 |
3 | This is the card form component.
4 |
5 | ```iframe
6 | sourcecodeSelector: '#view'
7 | dynamicSource: true
8 | title: Card Form
9 | renderer: nunjucks
10 | template: jsx.html
11 | bundle: card-form
12 | ```
13 |
--------------------------------------------------------------------------------
/styleguide/pages/modal.md:
--------------------------------------------------------------------------------
1 | # Modal Dialogue
2 |
3 | This is the modal dialogue component.
4 |
5 | ```iframe
6 | sourcecodeSelector: '#modal'
7 | dynamicSource: true
8 | title: Modal Dialogue
9 | renderer: nunjucks
10 | template: jsx.html
11 | bundle: modal
12 | ```
13 |
--------------------------------------------------------------------------------
/styleguide/pages/spinner.md:
--------------------------------------------------------------------------------
1 | # Spinner
2 |
3 | This is the spinner component.
4 |
5 | ```iframe
6 | sourcecodeSelector: '#view'
7 | dynamicSource: true
8 | title: Spinner
9 | renderer: nunjucks
10 | template: jsx.html
11 | bundle: spinner
12 | ```
13 |
--------------------------------------------------------------------------------
/styleguide/pages/typography.md:
--------------------------------------------------------------------------------
1 | # Typography
2 |
3 | This is an example of what the fonts look like using some basic markup.
4 |
5 | ```iframe
6 | renderer: nunjucks
7 | template: typography.html
8 | ```
9 |
--------------------------------------------------------------------------------
/styleguide/templates/base-styleguide.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ title }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {% block page %}{% endblock %}
20 |
21 |
22 | {% block script %}
23 | {% endblock %}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/styleguide/templates/buttons-spinner.html:
--------------------------------------------------------------------------------
1 | {% extends 'base-styleguide.html' %}
2 |
3 | {% block page %}
4 | Buttons With Spinners
5 | Standard Button
6 | Button with Spinner
7 | Button (disabled)
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/styleguide/templates/buttons.html:
--------------------------------------------------------------------------------
1 | {% extends 'base-styleguide.html' %}
2 |
3 | {% block page %}
4 | Buttons
5 | Button
6 | Button (disabled)
7 |
8 | Link Buttons
9 | Button Link
10 | Button Link (disabled)
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/styleguide/templates/jsx.html:
--------------------------------------------------------------------------------
1 | {% extends 'base-styleguide.html' %}
2 |
3 | {% block script %}
4 |
5 |
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/styleguide/templates/typography.html:
--------------------------------------------------------------------------------
1 | {% extends 'base-styleguide.html' %}
2 |
3 | {% block page %}
4 | Heading Level 1
5 | Heading Level 2
6 | Heading Level 3
7 | Heading Level 4
8 | Heading Level 5
9 | Heading Level 6
10 |
11 | The quick brown fox jumps over the lazy dog
12 | The quick brown fox jumps over the lazy dog (emphasis)
13 | The quick brown fox jumps over the lazy dog (strong)
14 | {% endblock %}
15 |
--------------------------------------------------------------------------------
/tasks/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | },
5 | "rules": {
6 | "eol-last": 0,
7 | "comma-dangle": 0,
8 | "strict": [2, "never"],
9 | "max-len": 0,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tasks/abideCompile.js:
--------------------------------------------------------------------------------
1 | // TODO when we know what format we want.
2 |
--------------------------------------------------------------------------------
/tasks/abideCreate.js:
--------------------------------------------------------------------------------
1 | var settings = require('../public/js/settings');
2 |
3 | module.exports = {
4 | default: { // Target name.
5 | options: {
6 | template: '../payments-l10n/locale/templates/LC_MESSAGES/payments-ui.pot',
7 | languages: settings.supportedLanguages,
8 | localeDir: 'locale',
9 | }
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/tasks/abideExtract.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | js: {
3 | src: [
4 | 'public/dist/*.js',
5 | ],
6 | dest: '../payments-l10n/locale/templates/LC_MESSAGES/payments-ui.pot',
7 | options: {
8 | language: 'JavaScript',
9 | }
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/tasks/abideMerge.js:
--------------------------------------------------------------------------------
1 | // TODO: when we know what we're doing.
2 |
--------------------------------------------------------------------------------
/tasks/clean.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | deps: [
3 | 'public/lib',
4 | 'public/scss/lib',
5 | 'public/fonts',
6 | ],
7 | dist: [
8 | 'public/dist'
9 | ]
10 | };
11 |
--------------------------------------------------------------------------------
/tasks/cog.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styleguide: {
3 | src: 'styleguide',
4 | options: {
5 | sourcecodeSelector: '#view',
6 | templateGlobals: {
7 | projectName: 'Payments UI Styleguide',
8 | appMedia: '../static',
9 | },
10 | templateConfig: {
11 | templatePaths: ['templates'],
12 | },
13 | copy: [{
14 | src: '../public/',
15 | target: 'static/public',
16 | }, {
17 | src: 'jsx-bundles/',
18 | target: 'static/jsx',
19 | }],
20 | },
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/tasks/concurrent.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | options: {
3 | logConcurrentOutput: true
4 | },
5 | styleguide: {
6 | tasks: ['watch:sass', 'watch:styleguide'],
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/tasks/copy.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | nodedeps: {
3 | files: [{
4 | cwd: 'node_modules/normalize.css/',
5 | expand: true,
6 | src: 'normalize.css',
7 | dest: 'public/scss/lib/',
8 | ext: '.scss',
9 | }, {
10 | cwd: 'node_modules/connect-fonts-clearsans/fonts/default/',
11 | expand: true,
12 | src: '*',
13 | dest: 'public/fonts/clearsans/',
14 | }, {
15 | cwd: 'node_modules/connect-fonts-firasans/fonts/default/',
16 | expand: true,
17 | src: '*',
18 | dest: 'public/fonts/firasans/',
19 | }, {
20 | cwd: 'node_modules/mozilla-tabzilla/',
21 | expand: true,
22 | src: ['**/*.png'],
23 | dest: 'public/',
24 | }, {
25 | cwd: 'node_modules/mozilla-tabzilla/css/',
26 | expand: true,
27 | ext: '.scss',
28 | src: ['**/*.css'],
29 | dest: 'public/scss/lib/',
30 | }],
31 |
32 | },
33 |
34 | };
35 |
--------------------------------------------------------------------------------
/tasks/csslint.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | lax: {
3 | options: {
4 | csslintrc: '.csslintrc'
5 | },
6 | src: ['public/dist/main.css']
7 | },
8 | strict: {
9 | options: {
10 | 'adjoining-classes': false,
11 | 'box-sizing': false,
12 | 'bulletproof-font-face': false,
13 | },
14 | src: ['public/dist/main.css']
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/tasks/devserver.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | options: {
3 | base: 'styleguide/build',
4 | type: 'http',
5 | port: 4001,
6 | async: false,
7 | },
8 | server: {}
9 | };
10 |
--------------------------------------------------------------------------------
/tasks/eslint.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | js: {
3 | src: [
4 | 'public/js/**/*.js*',
5 | '!public/js/bundle.js',
6 | 'tests/**/*.js*',
7 | 'Gruntfile.js',
8 | 'tasks/*.js',
9 | ],
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/tasks/gh-pages.js:
--------------------------------------------------------------------------------
1 | // get a formatted commit message to review changes from the commit log
2 | // github will turn some of these into clickable links
3 | function getDeployMessage() {
4 | var ret = '\n\n';
5 | if (process.env.TRAVIS !== 'true') {
6 | ret += 'missing env vars for travis-ci';
7 | return ret;
8 | }
9 | ret += 'branch: ' + process.env.TRAVIS_BRANCH + '\n';
10 | ret += 'SHA: ' + process.env.TRAVIS_COMMIT + '\n';
11 | ret += 'range SHA: ' + process.env.TRAVIS_COMMIT_RANGE + '\n';
12 | ret += 'build id: ' + process.env.TRAVIS_BUILD_ID + '\n';
13 | ret += 'build number: ' + process.env.TRAVIS_BUILD_NUMBER + '\n';
14 | return ret;
15 | }
16 |
17 | module.exports = {
18 | styleguide: {
19 | options: {
20 | base: 'styleguide/build',
21 | message: 'Updating docs',
22 | repo: 'git@github.com:mozilla/payments-ui.git'
23 | },
24 | src: ['**'],
25 | },
26 | // This is a special branch that contains the built files.
27 | docker: {
28 | options: {
29 | // silent option prevents decrypted credentials leaking into
30 | // travis logs. See https://github.com/tschaub/grunt-gh-pages#optionssilent
31 | silent: true,
32 | branch: 'docker-hub-build',
33 | clone: '.grunt/grunt-gh-pages/docker-hub-build/',
34 | user: {
35 | name: process.env.GH_USER,
36 | email: process.env.GH_EMAIL,
37 | },
38 | repo: 'https://' + process.env.GH_TOKEN + '@github.com/mozilla/payments-ui.git',
39 | message: 'publish docker build branch (auto)' + getDeployMessage(),
40 | },
41 | src: ['public/**/*', 'Dockerfile'],
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/tasks/karma.js:
--------------------------------------------------------------------------------
1 | var grunt = require('grunt');
2 |
3 | module.exports = {
4 | options: {
5 | logLevel: grunt.option('log-level') || 'INFO',
6 | },
7 | dev: {
8 | configFile: 'karma.conf.js',
9 | singleRun: false,
10 | autoWatch: true
11 | },
12 | run: {
13 | configFile: 'karma.conf.js',
14 | },
15 | sauce: {
16 | configFile: 'karma.conf.sauce.js',
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/tasks/sass.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var env_ = 'local';
4 |
5 | if (process.env.DEV) {
6 | env_ = 'dev';
7 | }
8 |
9 |
10 | module.exports = {
11 | options: {
12 | sourceMap: true,
13 | includePaths: [
14 | path.join('public/scss/config/', env_),
15 | ]
16 | },
17 | dev: {
18 | options: {
19 | outputStyle: 'expanded'
20 | },
21 | files: {
22 | // Common CSS shared between transaction and management app.
23 | 'public/dist/common.css': 'public/scss/common.scss',
24 | // Individual app CSS
25 | 'public/dist/transaction.css': 'public/scss/transaction.scss',
26 | 'public/dist/management.css': 'public/scss/management.scss',
27 | },
28 | },
29 | min: {
30 | options: {
31 | outputStyle: 'compressed'
32 | },
33 | files: {
34 | 'public/dist/common.min.css': 'public/scss/common.scss',
35 | 'public/dist/transaction.min.css': 'public/scss/transaction.scss',
36 | 'public/dist/management.min.css': 'public/scss/management.scss',
37 | },
38 | },
39 | email: {
40 | options: {
41 | outputStyle: 'expanded'
42 | },
43 | files: {
44 | 'public/dist/email.css': 'public/scss/email.scss',
45 | },
46 | },
47 | };
48 |
--------------------------------------------------------------------------------
/tasks/svg_sprite.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | paymethods: {
3 | expand: true,
4 | cwd: 'node_modules/payment-icons/min/flat',
5 | src: ['*.svg'],
6 | dest: 'public',
7 | options: {
8 | shape: {
9 | dimension: {
10 | maxWidth: 40,
11 | maxHeight: 40
12 | },
13 | dest: 'svg'
14 | },
15 | mode: {
16 | css: {
17 | layout: 'vertical',
18 | common: 'pay-method-icon',
19 | dimensions: true,
20 | padding: 10,
21 | prefix: '.pmtype-%s',
22 | dest: 'scss',
23 | sprite: '../img/pay-method-sprite.svg',
24 | bust: false,
25 | render: {
26 | scss: {
27 | dest: '_pay-method-sprite.scss'
28 | }
29 | }
30 | }
31 | }
32 | }
33 | },
34 | management: {
35 | expand: true,
36 | cwd: 'public/img/management',
37 | src: ['*.svg'],
38 | dest: 'public',
39 | options: {
40 | shape: {
41 | dimension: {
42 | maxWidth: 28,
43 | maxHeight: 28
44 | },
45 | dest: 'svg'
46 | },
47 | mode: {
48 | css: {
49 | layout: 'vertical',
50 | common: 'nav:after',
51 | dimensions: true,
52 | padding: 20,
53 | prefix: '.%s:after',
54 | dest: 'scss',
55 | sprite: '../img/mgmt-nav-sprite.svg',
56 | bust: false,
57 | render: {
58 | scss: {
59 | dest: '_mgmt-nav-sprite.scss'
60 | }
61 | }
62 | }
63 | }
64 | }
65 | }
66 |
67 | };
68 |
--------------------------------------------------------------------------------
/tasks/watch.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | sass: {
3 | files: 'public/scss/**/*.scss',
4 | tasks: ['sass:dev'],
5 | options: {
6 | debounceDelay: 250
7 | }
8 | },
9 | sassemail: {
10 | files: 'public/scss/**/*.scss',
11 | tasks: ['sass:email'],
12 | options: {
13 | debounceDelay: 250
14 | }
15 | },
16 | styleguide: {
17 | files: [
18 | 'public/dist/**/*',
19 | 'public/img/**/*',
20 | 'public/svg/**/*',
21 | 'styleguide/**/*',
22 | '!styleguide/build/**/*',
23 | ],
24 | tasks: ['cog'],
25 | options: {
26 | debounceDelay: 250
27 | }
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/tasks/webpack-dev-server.js:
--------------------------------------------------------------------------------
1 | var merge = require('lodash.merge');
2 | var webpack = require('webpack');
3 | var webpackConfig = require('../webpack.config');
4 |
5 | // Update webpack config with hot module loading config.
6 | var newWebpackConfig = merge({}, webpackConfig);
7 | newWebpackConfig.devtool = 'eval';
8 | newWebpackConfig.plugins.push(new webpack.NoErrorsPlugin());
9 | newWebpackConfig.module.loaders[0].loaders.unshift('react-hot');
10 |
11 |
12 | module.exports = {
13 | options: {
14 | host: 'localhost',
15 | contentBase: 'public/',
16 | historyApiFallback: true,
17 | hot: true,
18 | inline: true,
19 | publicPath: '/dist/',
20 | },
21 | start: {
22 | webpack: newWebpackConfig,
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/tasks/webpack.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | var webpackConfig = require('../webpack.config.js');
5 |
6 |
7 | module.exports = {
8 | options: webpackConfig,
9 | dev: {
10 | // Default to all the options in webpackConfig
11 | },
12 | prod: {
13 | output: {
14 | path: path.join(__dirname, '../public/dist/'),
15 | filename: '[name].bundle.min.js',
16 | },
17 | plugins: [
18 | new webpack.optimize.UglifyJsPlugin({minimize: true})
19 | ],
20 | },
21 | styleguide: {
22 | entry: {
23 | // Explicit entries for the styleguide.
24 | 'modal': './styleguide/jsx/modal',
25 | 'card-form': './styleguide/jsx/card-form',
26 | 'spinner': './styleguide/jsx/spinner',
27 | },
28 | output: {
29 | path: path.join(__dirname, '../styleguide/jsx-bundles/'),
30 | filename: '[name].bundle.js',
31 | chunkFilename: '[id].chunk.js',
32 | },
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/tests/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true,
4 | "node": true,
5 | },
6 | "rules": {
7 | "strict": [2, "never"],
8 | },
9 | "globals": {
10 | "assert": false,
11 | "sinon": false,
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tests/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/payments-ui/5cd67c44637259b457d051d633f5e70311a838f5/tests/.gitkeep
--------------------------------------------------------------------------------
/tests/actions/test.api.js:
--------------------------------------------------------------------------------
1 | import * as api from 'actions/api';
2 |
3 |
4 | describe('api', () => {
5 | describe('fetch', () => {
6 |
7 | var fakeSettings;
8 | var fakeJquery;
9 |
10 | beforeEach(() => {
11 | fakeSettings = {apiPrefix: 'http://not-a-real-api.com/api'};
12 | fakeJquery = {ajax: sinon.stub()};
13 | });
14 |
15 | function fetch(request={url: '/any/path/'}, opt=null) {
16 | if (!opt) {
17 | opt = {
18 | settings: fakeSettings,
19 | jquery: fakeJquery,
20 | csrfToken: false,
21 | };
22 | }
23 | return api.fetch(request, opt);
24 | }
25 |
26 | it('prefixes URLs with the API host', () => {
27 | var request = {url: '/some/service'};
28 | fetch(request);
29 |
30 | assert.equal(fakeJquery.ajax.firstCall.args[0].url,
31 | fakeSettings.apiPrefix + request.url);
32 | });
33 |
34 | it('does not send credentials when CORS is not allowed', () => {
35 | fakeSettings.allowCORSRequests = false;
36 | fetch();
37 | assert.equal(fakeJquery.ajax.firstCall.args[0].xhrFields.withCredentials,
38 | undefined);
39 | });
40 |
41 | it('sends credentials when CORS is allowed', () => {
42 | fakeSettings.allowCORSRequests = true;
43 | fetch();
44 | assert.equal(fakeJquery.ajax.firstCall.args[0].xhrFields.withCredentials,
45 | true);
46 | });
47 |
48 | it('requires a CSRF token', () => {
49 | assert.throws(
50 | () => fetch({url: '/some/service'}, {csrfToken: undefined}),
51 | /You must set a CSRF token/
52 | );
53 | });
54 |
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/tests/actions/test.braintree.js:
--------------------------------------------------------------------------------
1 | import * as braintreeActions from 'actions/braintree';
2 | import * as errorCodes from 'constants/error-codes';
3 |
4 | import * as helpers from '../helpers';
5 |
6 |
7 | const fakeCreditCard = {
8 | number: '411111111111',
9 | cvv: '222',
10 | expiration: '06/16',
11 | };
12 |
13 |
14 | describe('braintreeActions', function() {
15 | var dispatchSpy;
16 |
17 | beforeEach(function() {
18 | dispatchSpy = sinon.spy();
19 | });
20 |
21 | function tokenizeCreditCard({dispatch=dispatchSpy,
22 | braintreeToken='braintree-token',
23 | creditCard=fakeCreditCard,
24 | BraintreeClient=helpers.FakeBraintreeClient,
25 | callback=() => {}} = {}) {
26 | braintreeActions.tokenizeCreditCard({dispatch: dispatch,
27 | braintreeToken: braintreeToken,
28 | creditCard: creditCard,
29 | callback: callback,
30 | BraintreeClient: BraintreeClient});
31 | }
32 |
33 | describe('tokenizeCreditCard', function() {
34 |
35 | it('should tokenize a credit card', function() {
36 | var tokenizeSpy = sinon.spy();
37 | class FakeClient {
38 | tokenizeCard = tokenizeSpy;
39 | }
40 |
41 | tokenizeCreditCard({BraintreeClient: FakeClient});
42 |
43 | var request = tokenizeSpy.firstCall.args[0];
44 | assert.equal(request.number, fakeCreditCard.number);
45 | assert.equal(request.expirationDate, fakeCreditCard.expiration);
46 | assert.equal(request.cvv, fakeCreditCard.cvv);
47 | });
48 |
49 | it('should call back with pay method nonce', function() {
50 | var callbackSpy = sinon.spy();
51 | tokenizeCreditCard({callback: callbackSpy});
52 | assert.equal(callbackSpy.firstCall.args[0], 'some-nonce');
53 | });
54 |
55 | it('should throw with a bad card', function() {
56 |
57 | // Bad card with missing cvv
58 | // and expiration.
59 | var fakeBadCard = {
60 | number: '411111111111',
61 | };
62 |
63 | assert.throws(() => {
64 | tokenizeCreditCard({creditCard: fakeBadCard});
65 | }, Error, /Invalid card object/);
66 | });
67 |
68 | it('should dispatch an error on tokenization failure', function() {
69 | tokenizeCreditCard({BraintreeClient: helpers.FakeBraintreeClientError});
70 | var action = dispatchSpy.firstCall.args[0];
71 | helpers.assertNotificationErrorCode(
72 | action, errorCodes.BRAINTREE_TOKENIZATION_ERROR);
73 | });
74 |
75 | });
76 |
77 | });
78 |
--------------------------------------------------------------------------------
/tests/actions/test.notification-actions.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 | import * as errorCodes from 'constants/error-codes';
3 | import * as notificationActions from 'actions/notifications';
4 |
5 | import * as helpers from '../helpers';
6 |
7 |
8 | describe('notificationActions', function() {
9 |
10 | function removeNotification(id) {
11 | return notificationActions.removeNotification(id);
12 | }
13 |
14 | it('should dispatch ADD_NOTIFICATION', function() {
15 | var action = notificationActions.showError({
16 | errorCode: errorCodes.PRODUCT_ID_INVALID,
17 | text: 'whatever',
18 | });
19 | helpers.assertNotificationErrorCode(
20 | action, errorCodes.PRODUCT_ID_INVALID);
21 | });
22 |
23 | it('should default to autoHide for info notification', function() {
24 | var action = notificationActions.showInfo({text: 'whatever'});
25 | assert.equal(action.data.autoHide, true);
26 | });
27 |
28 | it('should default to being dismissable for error notification', function() {
29 | var action = notificationActions.showError({
30 | errorCode: errorCodes.PRODUCT_ID_INVALID,
31 | });
32 | assert.equal(action.data.userDismissable, true);
33 | });
34 |
35 | it('should throw on missing text in info notification', function() {
36 | assert.throws(() => {
37 | notificationActions.showInfo();
38 | }, Error, /requires text/);
39 | });
40 |
41 | it('should throw on missing errorCode in error', function() {
42 | assert.throws(() => {
43 | notificationActions.showError();
44 | }, Error, /requires an errorCode/);
45 | });
46 |
47 | it('sets default error message', function() {
48 | var action = notificationActions.showError({
49 | errorCode: errorCodes.PRODUCT_ID_INVALID,
50 | });
51 | assert.include(action.data.text, 'Internal error');
52 | });
53 |
54 | it('should dispatch user REMOVE_NOTIFICATION', function() {
55 | var action = removeNotification('awooga');
56 | assert.deepEqual(action, {
57 | id: 'awooga',
58 | type: actionTypes.REMOVE_NOTIFICATION,
59 | });
60 | });
61 |
62 | });
63 |
--------------------------------------------------------------------------------
/tests/actions/test.processing-actions.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 | import { beginProcessing, stopProcessing } from 'actions/processing';
3 |
4 |
5 | describe('Processing Actions', function() {
6 |
7 | it('should dispatch a begin processing action', function() {
8 | assert.deepEqual(beginProcessing('proc-id'), {
9 | type: actionTypes.BEGIN_PROCESSING,
10 | processingId: 'proc-id',
11 | });
12 | });
13 |
14 | it('should dispatch a stop processing action', function() {
15 | assert.deepEqual(stopProcessing('proc-id'), {
16 | type: actionTypes.STOP_PROCESSING,
17 | processingId: 'proc-id',
18 | });
19 | });
20 |
21 | it('should throw an error for empty processing IDs', function() {
22 | assert.throws(() => beginProcessing(null), Error,
23 | /processingId cannot be empty/);
24 | });
25 |
26 | });
27 |
--------------------------------------------------------------------------------
/tests/apps/test.management-app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 | import { Provider } from 'react-redux';
4 |
5 | import { createReduxStore } from 'data-store';
6 |
7 | import * as actionTypes from 'constants/action-types';
8 | import * as mgmtActions from 'actions/management';
9 | import ManagementApp from 'apps/management/app';
10 |
11 | import * as helpers from '../helpers';
12 |
13 |
14 | describe('management/app', function() {
15 | var fakeWindow;
16 | var FakeManagement;
17 | var FakeManageCards;
18 | var FakeSignIn;
19 | var store;
20 |
21 | beforeEach(function() {
22 | fakeWindow = {
23 | location: {
24 | href: '/',
25 | },
26 | };
27 | FakeManagement = helpers.stubComponent();
28 | FakeManageCards = helpers.stubComponent();
29 | FakeSignIn = helpers.stubComponent();
30 | store = createReduxStore();
31 | });
32 |
33 | function mountView({ window = fakeWindow } = {}) {
34 |
35 | var container = TestUtils.renderIntoDocument(
36 |
37 | {() => }
43 |
44 | );
45 | return TestUtils.findRenderedComponentWithType(
46 | container, ManagementApp
47 | );
48 | }
49 |
50 | it('should sign-in with access token', function() {
51 | var token = 'some-fxa-token';
52 |
53 | var view = mountView({
54 | window: {
55 | location: {
56 | href: '/?access_token=' + token,
57 | },
58 | },
59 | });
60 |
61 | var mgmt = TestUtils.findRenderedComponentWithType(
62 | view, FakeManagement
63 | );
64 | // The second child should be the sign-in component.
65 | // Make sure it gets the access token.
66 | var signIn = mgmt.props.children[1];
67 | assert.equal(signIn.props.accessToken, token);
68 |
69 | });
70 |
71 | it('should pass in card submission errors', function() {
72 | store.dispatch({
73 | type: actionTypes.GOT_BRAINTREE_TOKEN,
74 | braintreeToken: 'some-token',
75 | });
76 | store.dispatch(mgmtActions.showAddPayMethod());
77 | var apiError = {error_response: 'some error'};
78 | store.dispatch({
79 | type: actionTypes.CREDIT_CARD_SUBMISSION_ERRORS,
80 | apiErrorResult: apiError,
81 | });
82 | var view = mountView();
83 |
84 | var mgmt = TestUtils.findRenderedComponentWithType(
85 | view, FakeManagement
86 | );
87 | var addPayMethod = mgmt.props.children[1];
88 | assert.equal(addPayMethod.props.cardSubmissionErrors, apiError);
89 |
90 | });
91 |
92 | });
93 |
--------------------------------------------------------------------------------
/tests/components/test.modal.jsx:
--------------------------------------------------------------------------------
1 | import React, { findDOMNode } from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import Modal from 'components/modal';
5 |
6 | import * as helpers from '../helpers';
7 |
8 |
9 | describe('Modal', function() {
10 |
11 | beforeEach(function() {
12 | this.closeFunc = sinon.stub();
13 | this.Modal = TestUtils.renderIntoDocument(
14 |
15 |
16 |
Just some noddy content
17 |
Click me
18 |
19 |
20 | );
21 | this.eventStub = {
22 | preventDefault: sinon.stub(),
23 | stopPropagation: sinon.stub(),
24 | };
25 | });
26 |
27 | it('should have a title', function() {
28 | var title = helpers.findByTag(this.Modal, 'h2');
29 | assert.equal(findDOMNode(title).firstChild.nodeValue, 'whatever');
30 | });
31 |
32 | it('should have child content', function() {
33 | var content = helpers.findByClass(this.Modal, 'm-content');
34 | assert.equal(findDOMNode(content).nodeName.toLowerCase(), 'div');
35 | });
36 |
37 | it('should have child paragraph', function() {
38 | var text = helpers.findByClass(this.Modal, 'm-text');
39 | assert.equal(findDOMNode(text).firstChild.nodeValue,
40 | 'Just some noddy content');
41 | });
42 |
43 | it('should call close func when clicking the modal background', function() {
44 | var modal = helpers.findByClass(this.Modal, 'modal');
45 | TestUtils.Simulate.click(modal, this.eventStub);
46 | assert.ok(this.eventStub.preventDefault.called);
47 | assert.ok(this.eventStub.stopPropagation.called);
48 | assert.ok(this.closeFunc.called);
49 | });
50 |
51 | it('should call close func when clicking the close link', function() {
52 | var close = helpers.findByClass(this.Modal, 'close');
53 | TestUtils.Simulate.click(findDOMNode(close), this.eventStub);
54 | assert.ok(this.eventStub.preventDefault.called);
55 | assert.ok(this.eventStub.stopPropagation.called);
56 | assert.ok(this.closeFunc.called);
57 | });
58 |
59 | it('should not call close func when clicking other content.', function() {
60 | var otherLink = helpers.findByClass(this.Modal, 'other-link');
61 | TestUtils.Simulate.click(findDOMNode(otherLink), this.eventStub);
62 | assert.notOk(this.eventStub.preventDefault.called);
63 | assert.notOk(this.eventStub.stopPropagation.called);
64 | assert.notOk(this.closeFunc.called);
65 | });
66 |
67 | });
68 |
--------------------------------------------------------------------------------
/tests/components/test.notification-list.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import NotificationList from 'components/notification-list';
5 |
6 | import * as helpers from '../helpers';
7 |
8 |
9 | describe('NotificationList', function() {
10 |
11 | beforeEach(function() {
12 | this.removeNotification = sinon.stub();
13 | var MockCSSTransitionGroup = helpers.stubComponent('ul');
14 | var notifications = [
15 | ['foo', {
16 | type: 'error',
17 | text: 'error text',
18 | userDismissable: true,
19 | autoHide: false,
20 | },
21 | ], ['bar', {
22 | type: 'info',
23 | text: 'info text',
24 | userDismissable: false,
25 | autoHide: true,
26 | },
27 | ],
28 | ];
29 | this.NotificationList = TestUtils.renderIntoDocument(
30 |
35 | );
36 | console.log(React.findDOMNode(this.NotificationList));
37 | });
38 |
39 | it('should have two notifications', function() {
40 | var lis = helpers.findAllByTag(this.NotificationList, 'li');
41 | assert.equal(lis.length, 2);
42 | });
43 |
44 | it('should have an error notification', function() {
45 | assert.ok(helpers.findByClass(this.NotificationList, 'error'));
46 | });
47 |
48 | it('should have an info notification', function() {
49 | assert.ok(helpers.findByClass(this.NotificationList, 'info'));
50 | });
51 |
52 | });
53 |
54 |
--------------------------------------------------------------------------------
/tests/components/test.notification.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import Notification from 'components/notification';
5 | import { getId } from 'utils';
6 |
7 | import * as helpers from '../helpers';
8 |
9 |
10 | describe('Notification', function() {
11 |
12 | beforeEach(function() {
13 | this.removeNotification = sinon.stub();
14 | this.getNotification = ({ autoHide=false, userDismissable=false,
15 | type='info', text='hai' } = {}) => {
16 | return TestUtils.renderIntoDocument(
17 |
25 | );
26 | };
27 | });
28 |
29 | it('should have text', function() {
30 | var notification = this.getNotification({text: 'whatever'});
31 | assert.equal(
32 | helpers.findByClass(notification, 'text')
33 | .getDOMNode().textContent, 'whatever');
34 | });
35 |
36 | it('should have error class for type: error', function() {
37 | var notification = this.getNotification({type: 'error'});
38 | assert.ok(helpers.findByClass(notification, 'error'));
39 | });
40 |
41 | it('should have info class for type: info', function() {
42 | var notification = this.getNotification({type: 'info'});
43 | assert.ok(helpers.findByClass(notification, 'info'));
44 | });
45 |
46 | it('should have a dismiss link when userDismissable is true', function() {
47 | var notification = this.getNotification({userDismissable: true});
48 | assert.equal(
49 | helpers.findByTag(notification, 'a')
50 | .getDOMNode().getAttribute('class'), 'dismiss');
51 | });
52 |
53 | it('should call removeNotification with delay', function() {
54 | var notification = this.getNotification({autoHide: true});
55 | assert.equal(this.removeNotification.firstCall.args[0],
56 | notification.props.key);
57 | assert.equal(this.removeNotification.firstCall.args[1].delay, 5000);
58 | });
59 |
60 | it('should call removeNotificationClick on click', function() {
61 | var notification = this.getNotification({userDismissable: true});
62 | var link = helpers.findByTag(notification, 'a').getDOMNode();
63 | var prevDefault = sinon.stub();
64 | TestUtils.Simulate.click(link, {preventDefault: prevDefault});
65 | assert.ok(this.removeNotification.called);
66 | assert.ok(prevDefault.called);
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/tests/components/test.pay-method-icon.jsx:
--------------------------------------------------------------------------------
1 | import React, { findDOMNode } from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import PayMethodIcon from 'components/pay-method-icon';
5 |
6 | describe('Pay Method Icon', function() {
7 |
8 | var payMethodTypes = [
9 | 'amex',
10 | 'discover',
11 | 'jcb',
12 | 'maestro',
13 | 'mastercard',
14 | 'visa',
15 | ];
16 |
17 | function testPayMethod(payMethodType) {
18 | return function() {
19 | var PayMethodIcon_ = TestUtils.renderIntoDocument(
20 |
21 | );
22 | var payMethodIconNode = findDOMNode(PayMethodIcon_);
23 | assert.include(
24 | payMethodIconNode.getAttribute('class'), 'pmtype-' + payMethodType);
25 | };
26 | }
27 |
28 | payMethodTypes.forEach(function(payMethod) {
29 | it('sets payMethod class to: ' + payMethod, testPayMethod(payMethod));
30 | });
31 |
32 | });
33 |
--------------------------------------------------------------------------------
/tests/components/test.spinner.jsx:
--------------------------------------------------------------------------------
1 | import React, { findDOMNode } from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import * as helpers from '../helpers';
5 |
6 | import Spinner from 'components/spinner';
7 |
8 |
9 | describe('Spinner', function() {
10 |
11 | it('Uses loading as the default text', function() {
12 | var Spinner_ = TestUtils.renderIntoDocument( );
13 | var textNode = helpers.findByClass(Spinner_, 'text');
14 | assert.equal(findDOMNode(textNode).firstChild.nodeValue, 'Loading');
15 | });
16 |
17 | it('Uses custom text as supplied', function() {
18 | var Spinner_ = TestUtils.renderIntoDocument( );
19 | var textNode = helpers.findByClass(Spinner_, 'text');
20 | assert.equal(findDOMNode(textNode).firstChild.nodeValue, 'whatever');
21 | });
22 |
23 | });
24 |
--------------------------------------------------------------------------------
/tests/components/test.submit-button.js:
--------------------------------------------------------------------------------
1 | import React, { findDOMNode } from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import SubmitButton from 'components/submit-button';
5 |
6 |
7 | describe('SubmitButton', function() {
8 |
9 | it('uses submit as the default text', function() {
10 | var Button = TestUtils.renderIntoDocument( );
11 | assert.equal(findDOMNode(Button).firstChild.nodeValue, 'Submit');
12 | });
13 |
14 | it('uses custom text as supplied', function() {
15 | var Button = TestUtils.renderIntoDocument(
16 | );
17 | assert.equal(findDOMNode(Button).firstChild.nodeValue, 'whatever');
18 | });
19 |
20 | it('gets a css modifier class', function() {
21 | var Button = TestUtils.renderIntoDocument(
22 | );
23 | assert.include(findDOMNode(Button).className, 'some-modifier');
24 | });
25 |
26 | });
27 |
--------------------------------------------------------------------------------
/tests/reducers/test.app-reducer.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 | import appReducer from 'reducers/app';
3 | import { initialAppState } from 'reducers/app';
4 |
5 |
6 | describe('App Reducer', function() {
7 |
8 | it('should store CSRF token', function() {
9 | var app = appReducer(initialAppState, {
10 | type: actionTypes.GOT_CSRF_TOKEN,
11 | csrfToken: 'some-csrf-token',
12 | });
13 | assert.deepEqual(app, Object.assign({}, initialAppState, {
14 | csrfToken: 'some-csrf-token',
15 | }));
16 | });
17 |
18 | it('should add a notification', function() {
19 | var app = appReducer(initialAppState, {
20 | type: actionTypes.ADD_NOTIFICATION,
21 | data: {text: 'foo', type: 'info'},
22 | });
23 | assert.equal(app.notifications.length, 1);
24 | assert.equal(app.notifications[0].length, 2);
25 | assert.deepEqual(app.notifications[0][1],
26 | {text: 'foo', type: 'info'});
27 | });
28 |
29 | it('should remove a notification', function() {
30 | var app = appReducer(initialAppState, {
31 | type: actionTypes.ADD_NOTIFICATION,
32 | data: {text: 'foo', type: 'info'},
33 | });
34 | var id = app.notifications[0][0];
35 | assert.equal(app.notifications.length, 1);
36 | var app2 = appReducer(app, {
37 | type: actionTypes.REMOVE_NOTIFICATION,
38 | id: id,
39 | });
40 | assert.equal(app2.notifications.length, 0);
41 | });
42 |
43 | it('should not blow up removing a non-existant notification', function() {
44 | assert.doesNotThrow(() => {
45 | appReducer(initialAppState, {
46 | type: actionTypes.REMOVE_NOTIFICATION,
47 | id: 'blah',
48 | });
49 | });
50 | });
51 |
52 | });
53 |
--------------------------------------------------------------------------------
/tests/reducers/test.management-reducer.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 | import { default as management, initialMgmtState } from 'reducers/management';
3 |
4 |
5 | describe('management reducer', () => {
6 |
7 | it('sets a default view on sign-in', () => {
8 | assert.deepEqual(
9 | management(null, {type: actionTypes.USER_SIGNED_IN}),
10 | Object.assign({}, initialMgmtState, {
11 | tab: 'profile',
12 | view: actionTypes.SHOW_MY_ACCOUNT,
13 | })
14 | );
15 | });
16 |
17 | it('preserves view state on sign-in', () => {
18 | var existingState = Object.assign({}, initialMgmtState, {
19 | tab: 'pay-methods',
20 | view: actionTypes.SHOW_PAY_METHODS,
21 | });
22 | assert.deepEqual(
23 | management(existingState, {type: actionTypes.USER_SIGNED_IN}),
24 | existingState
25 | );
26 | });
27 |
28 | it('resets view state when showing sign-in', () => {
29 | var existingState = Object.assign({}, initialMgmtState, {
30 | tab: 'pay-methods',
31 | view: actionTypes.SHOW_PAY_METHODS,
32 | });
33 | assert.deepEqual(
34 | management(existingState, {type: actionTypes.SHOW_SIGN_IN}),
35 | initialMgmtState
36 | );
37 | });
38 |
39 | it('stores card submission errors', () => {
40 | var apiError = {error_response: 'invalid monkey name'};
41 | var state = Object.assign({}, initialMgmtState, {
42 | type: actionTypes.CREDIT_CARD_SUBMISSION_ERRORS,
43 | cardSubmissionErrors: apiError,
44 | });
45 | assert.deepEqual(state.cardSubmissionErrors, apiError);
46 | });
47 |
48 | it('preserves tab on error', () => {
49 | var existingState = Object.assign({}, initialMgmtState, {
50 | tab: 'pay-methods',
51 | });
52 | assert.strictEqual(
53 | management(existingState,
54 | {type: actionTypes.CREDIT_CARD_SUBMISSION_ERRORS}).tab,
55 | existingState.tab);
56 | });
57 |
58 | });
59 |
--------------------------------------------------------------------------------
/tests/reducers/test.processing-reducer.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 | import processingReducer from 'reducers/processing';
3 | import { initialProcessingState } from 'reducers/processing';
4 |
5 |
6 | describe('Processing Reducer', function() {
7 |
8 | it('should initialize state', function() {
9 | assert.deepEqual(processingReducer(undefined, {}),
10 | initialProcessingState);
11 | });
12 |
13 | it('should store true when a component begins processing', function() {
14 | var procId = 'some-id';
15 | var state = processingReducer(initialProcessingState, {
16 | type: actionTypes.BEGIN_PROCESSING,
17 | processingId: procId,
18 | });
19 | assert.equal(state[procId], true);
20 | });
21 |
22 | it('should clear state when a component stops processing', function() {
23 | var procId = 'some-id';
24 | var state = processingReducer(initialProcessingState, {
25 | type: actionTypes.BEGIN_PROCESSING,
26 | processingId: procId,
27 | });
28 | state = processingReducer(state, {
29 | type: actionTypes.STOP_PROCESSING,
30 | processingId: procId,
31 | });
32 | // This should now be deleted.
33 | assert.equal(state[procId], undefined);
34 | });
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/tests/reducers/test.trans-reducer.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from 'constants/action-types';
2 | import * as transactionActions from 'actions/transaction';
3 | import transactionReducer from 'reducers/transaction';
4 | import { initialTransState } from 'reducers/transaction';
5 |
6 |
7 | describe('Transaction Reducer', function() {
8 |
9 | it('should initialize a transaction', function() {
10 | var trans = transactionReducer(undefined, {});
11 | assert.deepEqual(trans, initialTransState);
12 | });
13 |
14 | it('should handle completed transactions', function() {
15 | var trans = transactionReducer(undefined, transactionActions.complete());
16 | assert.deepEqual(trans, Object.assign({}, initialTransState, {
17 | completed: true,
18 | }));
19 | });
20 |
21 | it('should store an optional email on completion', function() {
22 | var email = 'someone@somewhere.org';
23 | var trans = transactionReducer(
24 | undefined,
25 | transactionActions.complete({userEmail: email})
26 | );
27 | assert.deepEqual(trans, Object.assign({}, initialTransState, {
28 | completed: true,
29 | userEmail: email,
30 | }));
31 | });
32 |
33 | it('should set available methods to user saved pay methods', function() {
34 | var payMethods = [{type: 'Visa'}];
35 |
36 | var trans = transactionReducer(undefined, {
37 | type: actionTypes.USER_SIGNED_IN,
38 | user: {
39 | payMethods: payMethods,
40 | },
41 | });
42 |
43 | assert.deepEqual(trans, Object.assign({}, initialTransState, {
44 | availablePayMethods: payMethods,
45 | }));
46 | });
47 |
48 | });
49 |
--------------------------------------------------------------------------------
/tests/test-loader.js:
--------------------------------------------------------------------------------
1 | // Webpack tests entry point. Bundles all the test files
2 | // into a single file.
3 | // See: https://github.com/webpack/karma-webpack#alternative-usage
4 | import 'babel-core/polyfill';
5 |
6 | var logLevels = ['log', 'warn', 'info', 'error', 'debug'];
7 | logLevels.forEach(function(level) {
8 | console[level] = function() {};
9 | });
10 |
11 | var context = require.context('.', true, /.*?test\..*?.jsx?$/);
12 | context.keys().forEach(context);
13 |
--------------------------------------------------------------------------------
/tests/test.gh-pages-config.js:
--------------------------------------------------------------------------------
1 | import ghPagesConfig from '../tasks/gh-pages';
2 |
3 | describe('grunt-gh-pages config', function() {
4 | it('Docker builds should be silent=true', function() {
5 | assert.equal(ghPagesConfig.docker.options.silent, true);
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/tests/test.product-data.js:
--------------------------------------------------------------------------------
1 | import products from 'products';
2 |
3 |
4 | describe('products', function() {
5 |
6 | it('should have concrete brick data', function() {
7 | assert.equal(products['mozilla-concrete-brick'].id,
8 | 'mozilla-concrete-brick');
9 | });
10 |
11 | it('should have concrete mortar data', function() {
12 | assert.equal(products['mozilla-concrete-mortar'].id,
13 | 'mozilla-concrete-mortar');
14 | });
15 |
16 | });
17 |
--------------------------------------------------------------------------------
/tests/test.tracking.js:
--------------------------------------------------------------------------------
1 | import { Tracking } from 'tracking';
2 |
3 |
4 | describe('Tracking uninitialized', function() {
5 |
6 | it('should throw when calling setPage', function() {
7 | assert.throws(function() {
8 | var t = new Tracking({enabled: true});
9 | t.setPage('whatevar');
10 | }, /Must call init\(\) first/);
11 | });
12 |
13 | it('should throw when calling sendEvent', function() {
14 | assert.throws(function() {
15 | var t = new Tracking({enabled: true});
16 | t.sendEvent({
17 | category: 'cat',
18 | action: 'some-action',
19 | });
20 | }, /Must call init\(\) first/);
21 | });
22 |
23 | });
24 |
25 |
26 | describe('Tracking functions', function() {
27 |
28 | beforeEach(function() {
29 | this.t = new Tracking({
30 | id: 'whatever',
31 | enabled: true,
32 | });
33 | this.t.initialized = true;
34 | window.ga = sinon.stub();
35 | });
36 |
37 | it('should throw if page not set', function() {
38 | assert.throws(() => {
39 | this.t.setPage();
40 | }, Error, /page is required/);
41 | });
42 |
43 | it('should call ga', function() {
44 | this.t.setPage('whatever');
45 | assert.ok(window.ga.called);
46 | });
47 |
48 | it('should throw if category not set', function() {
49 | assert.throws(() => {
50 | this.t.sendEvent({});
51 | }, Error, /opts\.category is required/);
52 | });
53 |
54 | it('should throw if action not set', function() {
55 | assert.throws(() => {
56 | this.t.sendEvent({
57 | category: 'whatever',
58 | });
59 | }, Error, /opts\.action is required/);
60 | });
61 |
62 | it('should call _ga', function() {
63 | this.t.sendEvent({
64 | category: 'whatever',
65 | action: 'some-action',
66 | });
67 | assert.ok(window.ga.called);
68 | });
69 |
70 | });
71 |
--------------------------------------------------------------------------------
/tests/views/test.braintree-token.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import BraintreeToken from 'views/shared/braintree-token';
5 |
6 |
7 | describe('BraintreeToken', function() {
8 |
9 | var getBraintreeTokenSpy = sinon.spy();
10 |
11 | function mountView() {
12 | return TestUtils.renderIntoDocument(
13 |
14 | );
15 | }
16 |
17 | it('should sign in on mount', function() {
18 | mountView();
19 | assert.ok(getBraintreeTokenSpy.called);
20 | });
21 |
22 | });
23 |
--------------------------------------------------------------------------------
/tests/views/test.history.jsx:
--------------------------------------------------------------------------------
1 | import JsonTable from 'react-json-table';
2 | import React from 'react';
3 | import TestUtils from 'react/lib/ReactTestUtils';
4 |
5 | import Spinner from 'components/spinner';
6 | import History from 'views/management/history';
7 |
8 | import { transactionData } from '../actions/test.transaction';
9 |
10 |
11 | describe('History', function() {
12 | var fakeTransactionGetter;
13 |
14 | beforeEach(function() {
15 | fakeTransactionGetter = sinon.stub();
16 | });
17 |
18 | function mountView({transactions=null} = {}) {
19 | return TestUtils.renderIntoDocument(
20 |
24 | );
25 | }
26 |
27 | it('should show a spinner when loading transactions', function() {
28 | var view = mountView({transactions: null});
29 | var spinner = TestUtils.findRenderedComponentWithType(
30 | view, Spinner
31 | );
32 | assert.ok(spinner);
33 | });
34 |
35 | it('should load transactions on mount', function() {
36 | mountView();
37 | assert.equal(fakeTransactionGetter.called, true);
38 | });
39 |
40 | it('should show transactions', function() {
41 | var data = transactionData();
42 | var view = mountView({transactions: data});
43 | var table = TestUtils.findRenderedComponentWithType(
44 | view, JsonTable
45 | );
46 | assert.equal(table.props.rows, data);
47 | });
48 |
49 | it('should format transaction dates', function() {
50 | var data = transactionData();
51 | var view = mountView({transactions: data});
52 | var table = TestUtils.findRenderedComponentWithType(
53 | view, JsonTable
54 | );
55 |
56 | var cell;
57 | // Use the JsonTable configuration to test formatting of date values.
58 | table.props.columns.forEach(config => {
59 | if (config.key === 'created') {
60 | // Simulate how JsonTable formats a row.
61 | cell = config.cell({created: '2015-08-07T14:53:23.966'}, 'created');
62 | }
63 | });
64 |
65 | assert.equal(cell, 'Aug 7, 2015');
66 | });
67 |
68 | });
69 |
--------------------------------------------------------------------------------
/tests/views/test.management.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import { initialUserState } from 'reducers/user';
5 | import Management from 'views/management';
6 |
7 | import * as helpers from '../helpers';
8 |
9 |
10 | describe('Management', function() {
11 | var fakeSubscriptionGetter;
12 | var fakeShowSignOut;
13 | var fakeUser;
14 |
15 | beforeEach(function() {
16 | fakeSubscriptionGetter = sinon.stub();
17 | fakeShowSignOut = sinon.stub();
18 | fakeUser = Object.assign({}, initialUserState, {
19 | signedIn: true,
20 | email: 'f@f.com',
21 | });
22 | });
23 |
24 | function mountView() {
25 | return TestUtils.renderIntoDocument(
26 |
31 | );
32 | }
33 |
34 | it('should sign out on button click', function() {
35 | var view = mountView();
36 | var button = helpers.findById(view, 'show-sign-out');
37 |
38 | TestUtils.Simulate.click(button);
39 | assert.equal(fakeShowSignOut.called, true);
40 | });
41 |
42 | });
43 |
--------------------------------------------------------------------------------
/tests/views/test.pay-methods.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import PayMethods from 'views/management/pay-methods';
5 |
6 | import * as helpers from '../helpers';
7 |
8 |
9 | describe('Pay Methods', function() {
10 |
11 | beforeEach(function() {
12 | const fakeCards = [
13 | {
14 | id: 1,
15 | resource_uri: 'something',
16 | truncated_id: '4321',
17 | type_name: 'visa',
18 | }, {
19 | id: 2,
20 | resource_uri: 'whatever',
21 | truncated_id: '1234',
22 | type_name: 'amex',
23 | },
24 | ];
25 |
26 | this.getPayMethodsStub = sinon.stub();
27 | this.PayMethods = TestUtils.renderIntoDocument(
28 |
31 | );
32 | });
33 |
34 | it('should call getPayMethods stub', function() {
35 | assert.equal(this.getPayMethodsStub.called, true);
36 | });
37 |
38 | it('should have a card list', function() {
39 | var content = helpers.findByClass(this.PayMethods, 'pay-method-list');
40 | assert.ok(TestUtils.isDOMComponent(content));
41 | });
42 |
43 | it('should show no-results message', function() {
44 | var noPayMethods = TestUtils.renderIntoDocument(
45 |
48 | );
49 | var content = helpers.findByClass(noPayMethods, 'no-results');
50 | assert.ok(TestUtils.isDOMComponent(content));
51 | });
52 |
53 | it('should have disabled class on delete link if no cards', function() {
54 | var noPayMethods = TestUtils.renderIntoDocument(
55 |
58 | );
59 | var delButton = helpers.findByClass(noPayMethods, 'delete').getDOMNode();
60 | assert.ok(delButton.className.split(' ').indexOf('disabled') > -1);
61 | });
62 |
63 | });
64 |
--------------------------------------------------------------------------------
/tests/views/test.product-pay.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 | import { Provider } from 'react-redux';
4 |
5 | import { createReduxStore } from 'data-store';
6 | import CardForm from 'components/card-form';
7 | import ProductPay from 'views/transaction/product-pay';
8 |
9 |
10 | describe('ProductPay', function() {
11 |
12 | var fakePaymentProcessor;
13 | var defaultProductId = 'mozilla-concrete-brick';
14 | var braintreeToken = 'some-braintree-token';
15 | var store;
16 | var userDefinedAmount = '10.00';
17 |
18 | beforeEach(function() {
19 | fakePaymentProcessor = sinon.spy();
20 | store = createReduxStore();
21 | });
22 |
23 | function mountView({productId=defaultProductId} = {}) {
24 | var container = TestUtils.renderIntoDocument(
25 |
26 | {() => {
27 | return (
28 |
35 | );
36 | }}
37 |
38 | );
39 |
40 | return TestUtils.findRenderedComponentWithType(
41 | container, ProductPay
42 | );
43 | }
44 |
45 | it('should handle payment processing', function() {
46 | var View = mountView();
47 | var card = TestUtils.findRenderedComponentWithType(
48 | View, CardForm
49 | );
50 |
51 | var cardData = {number: '411111111111'};
52 | card.props.handleCardSubmit(cardData);
53 |
54 | assert.equal(fakePaymentProcessor.called, true);
55 | var args = fakePaymentProcessor.firstCall.args;
56 | assert.equal(args[0].userDefinedAmount, userDefinedAmount);
57 | assert.equal(args[0].productId, defaultProductId);
58 | assert.equal(args[0].creditCard, cardData);
59 | assert.equal(args[0].braintreeToken, braintreeToken);
60 | });
61 |
62 | it('should prompt for donation', function() {
63 | var View = mountView({productId: 'mozilla-foundation-donation'});
64 | var card = TestUtils.findRenderedComponentWithType(
65 | View, CardForm
66 | );
67 | assert.equal(card.props.submitPrompt, 'Donate now');
68 | });
69 |
70 | it('should prompt for recurring donation', function() {
71 | var View = mountView({productId: 'mozilla-foundation-recurring-donation'});
72 | var card = TestUtils.findRenderedComponentWithType(
73 | View, CardForm
74 | );
75 | assert.equal(card.props.submitPrompt, 'Donate now');
76 | });
77 |
78 | });
79 |
--------------------------------------------------------------------------------
/tests/views/test.sign-in.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import { initialUserState } from 'reducers/user';
5 | import SignIn from 'views/shared/sign-in';
6 |
7 |
8 | describe('SignIn', function() {
9 |
10 | var fakeAccessToken = 'some-oauth-access-token';
11 | var fakeError;
12 | var fakeTokenSignIn;
13 | var fakeUser;
14 | var fakeUserSignIn;
15 |
16 | beforeEach(function() {
17 | fakeError = sinon.spy();
18 | fakeTokenSignIn = sinon.spy();
19 | fakeUser = Object.assign({}, initialUserState);
20 | fakeUserSignIn = sinon.spy();
21 | });
22 |
23 | function mountView({accessToken=fakeAccessToken,
24 | user=fakeUser, allowUserSignIn} = {}) {
25 | return TestUtils.renderIntoDocument(
26 |
34 | );
35 | }
36 |
37 | it('should sign in on mount', function() {
38 | mountView();
39 | assert.equal(fakeTokenSignIn.firstCall.args[0], fakeAccessToken);
40 | });
41 |
42 | it('should do a token sign-in with access token', function() {
43 | var token = 'some-fxa-token';
44 | mountView({accessToken: token});
45 | assert.equal(fakeTokenSignIn.firstCall.args[0], token);
46 | });
47 |
48 | it('should do a user sign-in without access token', function() {
49 | mountView({accessToken: null});
50 | assert.equal(fakeUserSignIn.called, true);
51 | });
52 |
53 | it('should not sign in when user is already signed in', function() {
54 | fakeUser.signedIn = true;
55 | mountView();
56 | assert.equal(fakeUserSignIn.called, false);
57 | assert.equal(fakeTokenSignIn.called, false);
58 | });
59 |
60 | it('should error when user sign-in not allowed', function() {
61 | mountView({allowUserSignIn: false, accessToken: null});
62 | assert.equal(fakeUserSignIn.called, false);
63 | assert.equal(fakeTokenSignIn.called, false);
64 | assert.equal(fakeError.called, true);
65 | });
66 |
67 | });
68 |
--------------------------------------------------------------------------------
/tests/views/test.sign-out.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import { initialUserState } from 'reducers/user';
5 | import SignOut from 'views/shared/sign-out';
6 |
7 |
8 | describe('SignOut', function() {
9 |
10 | var fakeShowSignIn;
11 | var fakeUser;
12 | var fakeUserSignOut;
13 |
14 | beforeEach(function() {
15 | fakeShowSignIn = sinon.spy();
16 | fakeUserSignOut = sinon.spy();
17 | fakeUser = Object.assign({}, initialUserState, {
18 | signedIn: true,
19 | });
20 | });
21 |
22 | function mountView() {
23 | return TestUtils.renderIntoDocument(
24 |
29 | );
30 | }
31 |
32 | it('should sign out on mount', function() {
33 | mountView();
34 | assert.equal(fakeUserSignOut.called, true);
35 | });
36 |
37 | it('should not do sign-out when already signed out', function() {
38 | fakeUser.signedIn = false;
39 | mountView();
40 | assert.equal(fakeUserSignOut.called, false);
41 | });
42 |
43 | it('should sign back in on button click', function() {
44 | fakeUser.signedIn = false;
45 | var view = mountView();
46 | var button = TestUtils.findRenderedDOMComponentWithTag(
47 | view, 'a');
48 | TestUtils.Simulate.click(button);
49 | assert.equal(fakeShowSignIn.called, true);
50 | });
51 |
52 | });
53 |
--------------------------------------------------------------------------------
/tests/views/test.subscriptions.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react/lib/ReactTestUtils';
3 |
4 | import Subscriptions from 'views/management/subscriptions';
5 |
6 |
7 | describe('Subscriptions', function() {
8 |
9 | var getUserSubsSpy = sinon.stub();
10 |
11 | function mountView() {
12 | return TestUtils.renderIntoDocument(
13 |
14 | );
15 | }
16 |
17 | it('should call getUserSubscriptions on mount', function() {
18 | mountView();
19 | assert.equal(getUserSubsSpy.called, true);
20 | });
21 |
22 | });
23 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var webpack = require('webpack');
5 |
6 | module.exports = {
7 | devtool: 'source-map',
8 | entry: {
9 | 'management': './public/js/apps/management/main.jsx',
10 | 'transaction': './public/js/apps/transaction/main.jsx',
11 | },
12 | failOnError: true,
13 | output: {
14 | path: path.join(__dirname, 'public/dist/'),
15 | filename: '[name].bundle.js',
16 | publicPath: '/dist/',
17 | sourceMapFilename: '[file].map',
18 | },
19 | module: {
20 | loaders: [
21 | {
22 | exclude: /(node_modules|bower_components)/,
23 | test: /\.jsx?$/,
24 | // es7.objectRestSpread to enable ES7 rest spread operators
25 | // eg: let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
26 | loaders: ['babel?optional[]=es7.objectRestSpread&optional[]=es7.classProperties&stage=2'],
27 | },
28 | ],
29 | },
30 | plugins: [
31 | new webpack.optimize.CommonsChunkPlugin('commons.js',
32 | ['management', 'payment']),
33 | ],
34 | resolve: {
35 | // you can now require('file') instead of require('file.json')
36 | extensions: ['', '.js', '.jsx', '.json'],
37 | modulesDirectories: [
38 | 'public/js/',
39 | 'node_modules',
40 | 'node_modules/mozilla-payments-config/json/products/',
41 | ],
42 | },
43 | stats: {
44 | // Configure the console output
45 | colors: true,
46 | modules: true,
47 | reasons: true,
48 | },
49 | watch: true,
50 | };
51 |
--------------------------------------------------------------------------------