From 6713f3f8ecd33d2d9fff7b2563c29c1392f1b7b6 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Fri, 21 Aug 2020 17:16:59 +0300 Subject: [PATCH 01/29] Fix: remove douplicated page schema --- src/components/Page/Page.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/Page/Page.js b/src/components/Page/Page.js index 34243b6cc..35c99c10e 100644 --- a/src/components/Page/Page.js +++ b/src/components/Page/Page.js @@ -43,6 +43,14 @@ class PageComponent extends Component { // handling both dragover and drop events. document.addEventListener('dragover', preventDefault); document.addEventListener('drop', preventDefault); + + // Remove duplicated server-side rendered page schema. + // It's in to improve initial rendering performance, + // but after web app is initialized, react-helmet-async operates with + const pageSchema = document.getElementById('page-schema'); + if (pageSchema) { + pageSchema.remove(); + } } componentWillUnmount() { @@ -197,7 +205,7 @@ class PageComponent extends Component { {metaTags} - From f1019b46d42c6032c2df2335b7d490f015215f45 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Sun, 23 Aug 2020 23:27:52 +0300 Subject: [PATCH 02/29] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe96da63..64cdcf1d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ way to update this template, but currently, we follow a pattern: ## Upcoming version 2020-XX-XX +- [fix] Remove duplicate page schema from body. + [#1355](https://github.com/sharetribe/ftw-daily/pull/1355) + ## [v6.3.1] 2020-08-19 - [fix] Fix popup-button in SelectSingleFilterPopup.css and adjust Footer with correct baselines. From 0931dc516cc29a2b9625c5a62384122a2e3f493c Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Wed, 16 Sep 2020 16:22:51 +0300 Subject: [PATCH 03/29] Improve error handling by passing error details forward instead creating just "Local API error" --- server/api-util/sdk.js | 9 ++++++--- src/util/api.js | 15 +++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/server/api-util/sdk.js b/server/api-util/sdk.js index f2d6ff185..757c2f20f 100644 --- a/server/api-util/sdk.js +++ b/server/api-util/sdk.js @@ -55,13 +55,16 @@ exports.deserialize = str => { exports.handleError = (res, error) => { console.error(error); if (error.status && error.statusText && error.data) { + const { status, statusText, data } = error; + // JS SDK error res .status(error.status) .json({ - status: error.status, - statusText: error.statusText, - data: error.data, + name: 'Local API request failed', + status, + statusText, + data, }) .end(); } else { diff --git a/src/util/api.js b/src/util/api.js index 8a2af15fd..4e7254d25 100644 --- a/src/util/api.js +++ b/src/util/api.js @@ -48,14 +48,6 @@ const post = (path, body) => { }; return window .fetch(url, options) - .then(res => { - if (res.status >= 400) { - const e = new Error('Local API request failed'); - e.apiResponse = res; - throw e; - } - return res; - }) .then(res => { const contentTypeHeader = res.headers.get('Content-Type'); const contentType = contentTypeHeader ? contentTypeHeader.split(';')[0] : null; @@ -65,6 +57,13 @@ const post = (path, body) => { return res.json(); } return res.text(); + }) + .then(res => { + if (res.status >= 400) { + const e = res; + throw e; + } + return res; }); }; From 5518a5698377c3926995103e3f699f9efdc514eb Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Fri, 18 Sep 2020 16:48:38 +0300 Subject: [PATCH 04/29] Improve error handling by passing error details forward --- src/util/api.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/util/api.js b/src/util/api.js index 4e7254d25..9aecc3947 100644 --- a/src/util/api.js +++ b/src/util/api.js @@ -46,25 +46,25 @@ const post = (path, body) => { }, body: serialize(body), }; - return window - .fetch(url, options) - .then(res => { - const contentTypeHeader = res.headers.get('Content-Type'); - const contentType = contentTypeHeader ? contentTypeHeader.split(';')[0] : null; - if (contentType === 'application/transit+json') { - return res.text().then(deserialize); - } else if (contentType === 'application/json') { - return res.json(); - } - return res.text(); - }) - .then(res => { - if (res.status >= 400) { - const e = res; + return window.fetch(url, options).then(res => { + const contentTypeHeader = res.headers.get('Content-Type'); + const contentType = contentTypeHeader ? contentTypeHeader.split(';')[0] : null; + + if (res.status >= 400) { + return res.json().then(data => { + let e = new Error(); + e = Object.assign(e, data); + throw e; - } - return res; - }); + }); + } + if (contentType === 'application/transit+json') { + return res.text().then(deserialize); + } else if (contentType === 'application/json') { + return res.json(); + } + return res.text(); + }); }; // Fetch transaction line items from the local API endpoint. From 650491ab853a3452872ed51eab7f664431e2461f Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Fri, 18 Sep 2020 16:48:53 +0300 Subject: [PATCH 05/29] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64cdcf1d5..67ffa74d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ way to update this template, but currently, we follow a pattern: ## Upcoming version 2020-XX-XX +- [fix] Improve error handling by passing error details forward instead of creating a new error that + hides the details when making API call to FTW server. + [#1361](https://github.com/sharetribe/ftw-daily/pull/1361) - [fix] Remove duplicate page schema from body. [#1355](https://github.com/sharetribe/ftw-daily/pull/1355) From 2b850deca1765176628d7776bce3f7123d43ba21 Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 10:19:39 +0300 Subject: [PATCH 06/29] Fix missing props error in TransactionPage and TransactionPanel tests --- src/components/TransactionPanel/TransactionPanel.test.js | 4 ++++ src/containers/TransactionPage/TransactionPage.test.js | 4 ++++ .../__snapshots__/TransactionPage.test.js.snap | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/src/components/TransactionPanel/TransactionPanel.test.js b/src/components/TransactionPanel/TransactionPanel.test.js index 02e29a8f3..a791f9a4b 100644 --- a/src/components/TransactionPanel/TransactionPanel.test.js +++ b/src/components/TransactionPanel/TransactionPanel.test.js @@ -133,6 +133,8 @@ describe('TransactionPanel - Sale', () => { onSendReview: noop, onResetForm: noop, onSubmitBookingRequest: noop, + onFetchTransactionLineItems: noop, + fetchLineItemsInProgress: false, intl: fakeIntl, }; @@ -334,6 +336,8 @@ describe('TransactionPanel - Order', () => { acceptInProgress: false, declineInProgress: false, onSubmitBookingRequest: noop, + onFetchTransactionLineItems: noop, + fetchLineItemsInProgress: false, }; it('enquired matches snapshot', () => { diff --git a/src/containers/TransactionPage/TransactionPage.test.js b/src/containers/TransactionPage/TransactionPage.test.js index 358e48d96..acb585aae 100644 --- a/src/containers/TransactionPage/TransactionPage.test.js +++ b/src/containers/TransactionPage/TransactionPage.test.js @@ -53,6 +53,8 @@ describe('TransactionPage - Sale', () => { onShowMoreMessages: noop, onSendMessage: noop, onResetForm: noop, + onFetchTransactionLineItems: noop, + fetchLineItemsInProgress: false, intl: fakeIntl, location: { @@ -108,6 +110,8 @@ describe('TransactionPage - Order', () => { onShowMoreMessages: noop, onSendMessage: noop, onResetForm: noop, + onFetchTransactionLineItems: noop, + fetchLineItemsInProgress: false, intl: fakeIntl, acceptInProgress: false, diff --git a/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap b/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap index e3dca0672..acf675e96 100644 --- a/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap +++ b/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap @@ -48,6 +48,7 @@ exports[`TransactionPage - Order matches snapshot 1`] = ` declineInProgress={false} declineSaleError={null} fetchLineItemsError={null} + fetchLineItemsInProgress={false} fetchMessagesError={null} fetchMessagesInProgress={false} fetchTimeSlotsError={null} @@ -57,6 +58,7 @@ exports[`TransactionPage - Order matches snapshot 1`] = ` oldestMessagePageFetched={0} onAcceptSale={[Function]} onDeclineSale={[Function]} + onFetchTransactionLineItems={[Function]} onSendMessage={[Function]} onShowMoreMessages={[Function]} onSubmitBookingRequest={[Function]} @@ -274,6 +276,7 @@ exports[`TransactionPage - Sale matches snapshot 1`] = ` declineInProgress={false} declineSaleError={null} fetchLineItemsError={null} + fetchLineItemsInProgress={false} fetchMessagesError={null} fetchTimeSlotsError={null} initialMessageFailed={false} @@ -282,6 +285,7 @@ exports[`TransactionPage - Sale matches snapshot 1`] = ` oldestMessagePageFetched={0} onAcceptSale={[Function]} onDeclineSale={[Function]} + onFetchTransactionLineItems={[Function]} onSendMessage={[Function]} onShowMoreMessages={[Function]} onSubmitBookingRequest={[Function]} From 9b2ee22677d234fce4e1d670314363e19357e98a Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 10:21:26 +0300 Subject: [PATCH 07/29] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67ffa74d2..03e5d3cdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ way to update this template, but currently, we follow a pattern: ## Upcoming version 2020-XX-XX +- [fix] Fix missing proptype warnings in `TransactionPage` and `TransactionPanel` tests. + [#1363](https://github.com/sharetribe/ftw-daily/pull/1363) - [fix] Improve error handling by passing error details forward instead of creating a new error that hides the details when making API call to FTW server. [#1361](https://github.com/sharetribe/ftw-daily/pull/1361) From ad788327f4480d69d774da56673c71c7b6d06f71 Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 13:59:01 +0300 Subject: [PATCH 08/29] Update sharetribe-flex-sdk to version 1.13.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5aaf24858..a86e4f3c3 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "redux": "^4.0.5", "redux-thunk": "^2.3.0", "seedrandom": "^3.0.5", - "sharetribe-flex-sdk": "1.12.0", + "sharetribe-flex-sdk": "1.13.0", "sharetribe-scripts": "3.1.1", "smoothscroll-polyfill": "^0.4.0", "source-map-support": "^0.5.9", diff --git a/yarn.lock b/yarn.lock index bbf51cc68..5bf8169ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10475,10 +10475,10 @@ shallowequal@^1.1.0: resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== -sharetribe-flex-sdk@1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/sharetribe-flex-sdk/-/sharetribe-flex-sdk-1.12.0.tgz#072c5789ba089b49cd38918de30d9681ca5d7736" - integrity sha512-4OILWmbQiPU7epyg+QrjmOJx2mo01gL19AWnwjhSoGj8MO7HVILUOfZdnv/rbfPoyiT06fvxw9gZqgEtTZV72A== +sharetribe-flex-sdk@1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/sharetribe-flex-sdk/-/sharetribe-flex-sdk-1.13.0.tgz#c5a460eda2c751b6cc244237aaa8254caf741a9b" + integrity sha512-g8+Cx2m2TF/iUemYs97qWaJ2NdfCqiocm+tU5zC9RuSWMOY0eZ0NA8G6kg7/2EVzrZZJqDsNofdS4k2nkZ7p4w== dependencies: axios "^0.19.0" js-cookie "^2.1.3" From 76ac9d670ce0671e35d3975f44d580a3d6fc43c6 Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 10:41:02 +0300 Subject: [PATCH 09/29] Add loginWithIdp endpoint --- server/api/auth/loginWithIdp.js | 124 ++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 server/api/auth/loginWithIdp.js diff --git a/server/api/auth/loginWithIdp.js b/server/api/auth/loginWithIdp.js new file mode 100644 index 000000000..d939c7e5e --- /dev/null +++ b/server/api/auth/loginWithIdp.js @@ -0,0 +1,124 @@ +const http = require('http'); +const https = require('https'); +const sharetribeSdk = require('sharetribe-flex-sdk'); +const sdkUtils = require('../../api-util/sdk'); + +const CLIENT_ID = process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID; +const CLIENT_SECRET = process.env.SHARETRIBE_SDK_CLIENT_SECRET; +const TRANSIT_VERBOSE = process.env.REACT_APP_SHARETRIBE_SDK_TRANSIT_VERBOSE === 'true'; +const USING_SSL = process.env.REACT_APP_SHARETRIBE_USING_SSL === 'true'; +const BASE_URL = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL; +const rootUrl = process.env.REACT_APP_CANONICAL_ROOT_URL; + +// Instantiate HTTP(S) Agents with keepAlive set to true. +// This will reduce the request time for consecutive requests by +// reusing the existing TCP connection, thus eliminating the time used +// for setting up new TCP connections. +const httpAgent = new http.Agent({ keepAlive: true }); +const httpsAgent = new https.Agent({ keepAlive: true }); + +const baseUrl = BASE_URL ? { baseUrl: BASE_URL } : {}; + +module.exports = (err, user, req, res, clientID, idpId) => { + if (err) { + console.error(err); + + // Save error details to cookie so that we can show + // relevant information in the frontend + res + .cookie( + 'st-autherror', + { + status: err.status, + code: err.code, + message: err.message, + }, + { + maxAge: 15 * 60 * 1000, // 15 minutes + } + ) + .redirect(`${rootUrl}/login#`); + } + + if (!user) { + console.error('Failed to fetch user details from identity provider!'); + + // Save error details to cookie so that we can show + // relevant information in the frontend + res + .cookie( + 'st-autherror', + { + status: 'Bad Request', + code: 400, + message: 'Failed to fetch user details from identity provider!', + }, + { + maxAge: 15 * 60 * 1000, // 15 minutes + } + ) + .redirect(`${rootUrl}/login#`); + } + + const tokenStore = sharetribeSdk.tokenStore.expressCookieStore({ + clientId: CLIENT_ID, + req, + res, + secure: USING_SSL, + }); + + const sdk = sharetribeSdk.createInstance({ + transitVerbose: TRANSIT_VERBOSE, + clientId: CLIENT_ID, + clientSecret: CLIENT_SECRET, + httpAgent, + httpsAgent, + tokenStore, + typeHandlers: sdkUtils.typeHandlers, + ...baseUrl, + }); + + sdk + .loginWithIdp({ + idpId: 'facebook', + idpClientId: clientID, + idpToken: user ? user.accessToken : null, + }) + .then(response => { + if (response.status === 200) { + // If the user was authenticated, redirect back to to LandingPage + // We need to add # to the end of the URL because otherwise Facebook + // login will add their defaul #_#_ which breaks the routing in frontend. + + if (user.returnUrl) { + res.redirect(`${rootUrl}${user.returnUrl}#`); + } else { + res.redirect(`${rootUrl}/#`); + } + } + }) + .catch(() => { + // If authentication fails, we want to create a new user with idp + // For this we will need to pass some information to frontend so + // that we can use that information in createUserWithIdp api call. + // The createUserWithIdp api call is triggered from frontend + // after showing a confirm page to user + + res.cookie( + 'st-authinfo', + { + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + idpToken: `${user.accessToken}`, + idpId, + from: user.returnUrl, + }, + { + maxAge: 15 * 60 * 1000, // 15 minutes + } + ); + + res.redirect(`${rootUrl}/confirm#`); + }); +}; From e046871ad98b3be7bcd51a4b9db1c0015e1447b9 Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 10:45:23 +0300 Subject: [PATCH 10/29] Add createUserWithIdp endpoint --- server/api/auth/createUserWithIdp.js | 81 ++++++++++++++++++++++++++++ server/apiRouter.js | 4 ++ 2 files changed, 85 insertions(+) create mode 100644 server/api/auth/createUserWithIdp.js diff --git a/server/api/auth/createUserWithIdp.js b/server/api/auth/createUserWithIdp.js new file mode 100644 index 000000000..6270d674e --- /dev/null +++ b/server/api/auth/createUserWithIdp.js @@ -0,0 +1,81 @@ +const http = require('http'); +const https = require('https'); +const sharetribeSdk = require('sharetribe-flex-sdk'); +const { handleError, serialize, typeHandlers } = require('../../api-util/sdk'); + +const CLIENT_ID = process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID; +const CLIENT_SECRET = process.env.SHARETRIBE_SDK_CLIENT_SECRET; +const TRANSIT_VERBOSE = process.env.REACT_APP_SHARETRIBE_SDK_TRANSIT_VERBOSE === 'true'; +const USING_SSL = process.env.REACT_APP_SHARETRIBE_USING_SSL === 'true'; +const BASE_URL = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL; + +const FACBOOK_APP_ID = process.env.REACT_APP_FACEBOOK_APP_ID; +const GOOGLE_CLIENT_ID = process.env.REACT_APP_GOOGLE_CLIENT_ID; + +const FACEBOOK_IDP_ID = 'facebook'; +const GOOGLE_IDP_ID = 'google'; + +// Instantiate HTTP(S) Agents with keepAlive set to true. +// This will reduce the request time for consecutive requests by +// reusing the existing TCP connection, thus eliminating the time used +// for setting up new TCP connections. +const httpAgent = new http.Agent({ keepAlive: true }); +const httpsAgent = new https.Agent({ keepAlive: true }); + +const baseUrl = BASE_URL ? { baseUrl: BASE_URL } : {}; + +module.exports = (req, res) => { + const tokenStore = sharetribeSdk.tokenStore.expressCookieStore({ + clientId: CLIENT_ID, + req, + res, + secure: USING_SSL, + }); + + const sdk = sharetribeSdk.createInstance({ + transitVerbose: TRANSIT_VERBOSE, + clientId: CLIENT_ID, + clientSecret: CLIENT_SECRET, + httpAgent, + httpsAgent, + tokenStore, + typeHandlers, + ...baseUrl, + }); + + const { idpToken, idpId, ...rest } = req.body; + + // Choose the idpClientId based on which authentication method is used. + const idpClientId = + idpId === FACEBOOK_IDP_ID ? FACBOOK_APP_ID : idpId === GOOGLE_IDP_ID ? GOOGLE_CLIENT_ID : null; + + sdk.currentUser + .createWithIdp({ idpId: FACEBOOK_IDP_ID, idpClientId, idpToken, ...rest }) + .then(() => + // After the user is created, we need to call loginWithIdp endpoint + // so that the user will be logged in. + sdk.loginWithIdp({ + idpId, + idpClientId: `${idpClientId}`, + idpToken: `${idpToken}`, + }) + ) + .then(apiResponse => { + const { status, statusText, data } = apiResponse; + res + .clearCookie('st-authinfo') + .status(status) + .set('Content-Type', 'application/transit+json') + .send( + serialize({ + status, + statusText, + data, + }) + ) + .end(); + }) + .catch(e => { + handleError(res, e); + }); +}; diff --git a/server/apiRouter.js b/server/apiRouter.js index 1a09735cb..e51bf5af5 100644 --- a/server/apiRouter.js +++ b/server/apiRouter.js @@ -16,6 +16,8 @@ const transactionLineItems = require('./api/transaction-line-items'); const initiatePrivileged = require('./api/initiate-privileged'); const transitionPrivileged = require('./api/transition-privileged'); +const createUserWithIdp = require('./api/auth/createUserWithIdp'); + const router = express.Router(); // ================ API router middleware: ================ // @@ -50,4 +52,6 @@ router.post('/transaction-line-items', transactionLineItems); router.post('/initiate-privileged', initiatePrivileged); router.post('/transition-privileged', transitionPrivileged); +router.post('/auth/create-user-with-idp', createUserWithIdp); + module.exports = router; From dd4cff7664a5f93905edc1f1442a4dc62fe745b2 Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 13:12:12 +0300 Subject: [PATCH 11/29] util/api.js: Add createWithIdp endpoint --- src/util/api.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/util/api.js b/src/util/api.js index 9aecc3947..9dc560931 100644 --- a/src/util/api.js +++ b/src/util/api.js @@ -1,3 +1,7 @@ +// These helpers are calling FTW's own server-side routes +// and nor directly calling Marketplace API or Integration API +// You can find these api endpoints from 'server/api/...' directory + import { types as sdkTypes, transit } from './sdkLoader'; import config from '../config'; import Decimal from 'decimal.js'; @@ -98,3 +102,16 @@ export const initiatePrivileged = body => { export const transitionPrivileged = body => { return post('/api/transition-privileged', body); }; + +// Create user with identity provider (e.g. Facebook or Google) +// +// If loginWithIdp api call fails and user can't authenticate to Flex with idp +// we will show option to create a new user with idp. +// For that user needs to confirm data fetched from the idp. +// After the confrimation, this endpoint is called to create a new user with confirmed data. +// +// See `server/api/auth/createUserWithIdp.js` to see what data should +// be sent in the body. +export const createUserWithIdp = body => { + return post('/api/auth/create-user-with-idp', body); +}; From 51a3294d2e0b2b050713ab7aa54eb302337825f3 Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 10:55:59 +0300 Subject: [PATCH 12/29] Initialize PassportJS --- package.json | 1 + server/index.js | 4 ++++ yarn.lock | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/package.json b/package.json index a86e4f3c3..97a2489e3 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "moment": "^2.22.2", "object.entries": "^1.1.2", "object.values": "^1.1.1", + "passport": "^0.4.1", "path-to-regexp": "^6.1.0", "prop-types": "^15.7.2", "query-string": "^6.13.1", diff --git a/server/index.js b/server/index.js index fe00a801e..c13aa5df6 100644 --- a/server/index.js +++ b/server/index.js @@ -30,6 +30,7 @@ const enforceSsl = require('express-enforces-ssl'); const path = require('path'); const sharetribeSdk = require('sharetribe-flex-sdk'); const sitemap = require('express-sitemap'); +const passport = require('passport'); const auth = require('./auth'); const apiRouter = require('./apiRouter'); const renderer = require('./renderer'); @@ -145,6 +146,9 @@ if (!dev) { } } +// Initialize passport +app.use(passport.initialize()); + // Server-side routes that do not render the application app.use('/api', apiRouter); diff --git a/yarn.lock b/yarn.lock index 5bf8169ab..9ce3e1b76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8275,6 +8275,19 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +passport-strategy@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= + +passport@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" + integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + path-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" @@ -8353,6 +8366,11 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= + pbkdf2@^3.0.3: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" From 3a6ff060b9c30dfb4a95574cd22fec349e0b417c Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 10:58:51 +0300 Subject: [PATCH 13/29] Add Facebook strategy for authentication --- package.json | 1 + server/api/auth/facebook.js | 56 +++++++++++++++++++++++++++++++++++++ server/apiRouter.js | 15 ++++++++++ server/index.js | 5 +++- yarn.lock | 35 ++++++++++++++++++++++- 5 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 server/api/auth/facebook.js diff --git a/package.json b/package.json index 97a2489e3..8f9362cb3 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "object.entries": "^1.1.2", "object.values": "^1.1.1", "passport": "^0.4.1", + "passport-facebook": "^3.0.0", "path-to-regexp": "^6.1.0", "prop-types": "^15.7.2", "query-string": "^6.13.1", diff --git a/server/api/auth/facebook.js b/server/api/auth/facebook.js new file mode 100644 index 000000000..02e94eaf5 --- /dev/null +++ b/server/api/auth/facebook.js @@ -0,0 +1,56 @@ +const passport = require('passport'); +const passportFacebook = require('passport-facebook'); +const loginWithIdp = require('./loginWithIdp'); + +const radix = 10; +const PORT = parseInt(process.env.REACT_APP_DEV_API_SERVER_PORT, radix); +const rootUrl = process.env.REACT_APP_CANONICAL_ROOT_URL; +const clientID = process.env.REACT_APP_FACEBOOK_APP_ID; +const clientSecret = process.env.FACEBOOK_APP_SECRET; + +const FacebookStrategy = passportFacebook.Strategy; +let callbackURL = null; + +const useDevApiServer = process.env.NODE_ENV === 'development' && !!PORT; + +if (useDevApiServer) { + callbackURL = `http://localhost:${PORT}/api/auth/facebook/callback`; +} else { + callbackURL = `${rootUrl}/api/auth/facebook/callback`; +} + +const strategyOptions = { + clientID, + clientSecret, + callbackURL, + profileFields: ['id', 'name', 'emails'], +}; + +const verifyCallback = (accessToken, refreshToken, profile, done) => { + const { email, first_name, last_name } = profile._json; + + const userData = { + email, + firstName: first_name, + lastName: last_name, + accessToken, + refreshToken, + }; + + done(null, userData); +}; + +// ClientId is required when adding a new Facebook strategy to passport +if (clientID) { + passport.use(new FacebookStrategy(strategyOptions, verifyCallback)); +} + +exports.authenticateFacebook = passport.authenticate('facebook', { scope: ['email'] }); + +// Use custom callback for calling loginWithIdp enpoint +// to log in the user to Flex with the data from Facebook +exports.authenticateFacebookCallback = (req, res, next) => { + passport.authenticate('facebook', function(err, user) { + loginWithIdp(err, user, req, res, clientID, 'facebook'); + })(req, res, next); +}; diff --git a/server/apiRouter.js b/server/apiRouter.js index e51bf5af5..4d21d277f 100644 --- a/server/apiRouter.js +++ b/server/apiRouter.js @@ -18,6 +18,8 @@ const transitionPrivileged = require('./api/transition-privileged'); const createUserWithIdp = require('./api/auth/createUserWithIdp'); +const { authenticateFacebook, authenticateFacebookCallback } = require('./api/auth/facebook'); + const router = express.Router(); // ================ API router middleware: ================ // @@ -52,6 +54,19 @@ router.post('/transaction-line-items', transactionLineItems); router.post('/initiate-privileged', initiatePrivileged); router.post('/transition-privileged', transitionPrivileged); +// Create user with identity provider (e.g. Facebook or Google) +// This endpoint is called to create a new user after user has confirmed +// they want to continue with the data fetched from IdP (e.g. name and email) router.post('/auth/create-user-with-idp', createUserWithIdp); +// Facebook authentication endpoints + +// This endpoint is called when user wants to initiate authenticaiton with Facebook +router.get('/auth/facebook', authenticateFacebook); + +// This is the route for callback URL the user is redirected after authenticating +// with Facebook. In this route a Passport.js custom callback is used for calling +// loginWithIdp endpoint in Flex API to authenticate user to Flex +router.get('/auth/facebook/callback', authenticateFacebookCallback); + module.exports = router; diff --git a/server/index.js b/server/index.js index c13aa5df6..1450068df 100644 --- a/server/index.js +++ b/server/index.js @@ -146,7 +146,10 @@ if (!dev) { } } -// Initialize passport +// Initialize Passport.js (http://www.passportjs.org/) +// Passport is authentication middleware for Node.js +// We use passport to enable authenticating with +// a 3rd party identity provider (e.g. Facebook or Google) app.use(passport.initialize()); // Server-side routes that do not render the application diff --git a/yarn.lock b/yarn.lock index 9ce3e1b76..b743d394c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2240,6 +2240,11 @@ base64-js@^1.0.2: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== +base64url@3.x.x: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -7827,6 +7832,11 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +oauth@0.9.x: + version "0.9.15" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" + integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= + object-assign@4.1.1, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -8275,6 +8285,24 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +passport-facebook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/passport-facebook/-/passport-facebook-3.0.0.tgz#b16f7314128be55d020a2b75f574c194bd6d9805" + integrity sha512-K/qNzuFsFISYAyC1Nma4qgY/12V3RSLFdFVsPKXiKZt434wOvthFW1p7zKa1iQihQMRhaWorVE1o3Vi1o+ZgeQ== + dependencies: + passport-oauth2 "1.x.x" + +passport-oauth2@1.x.x: + version "1.5.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.5.0.tgz#64babbb54ac46a4dcab35e7f266ed5294e3c4108" + integrity sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ== + dependencies: + base64url "3.x.x" + oauth "0.9.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + passport-strategy@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" @@ -11497,6 +11525,11 @@ uglify-js@^3.1.4: commander "~2.20.0" source-map "~0.6.1" +uid2@0.0.x: + version "0.0.3" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" + integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I= + undefsafe@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" @@ -11701,7 +11734,7 @@ utila@^0.4.0, utila@~0.4: resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= -utils-merge@1.0.1: +utils-merge@1.0.1, utils-merge@1.x.x: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= From 0876e0d7b3bde90b00d12d14b9df7be6037e7714 Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 11:13:11 +0300 Subject: [PATCH 14/29] Create ConfirmSignupForm --- .../ConfirmSignupForm/ConfirmSignupForm.css | 54 ++++++ .../ConfirmSignupForm/ConfirmSignupForm.js | 174 ++++++++++++++++++ src/forms/index.js | 1 + src/translations/en.json | 15 +- 4 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 src/forms/ConfirmSignupForm/ConfirmSignupForm.css create mode 100644 src/forms/ConfirmSignupForm/ConfirmSignupForm.js diff --git a/src/forms/ConfirmSignupForm/ConfirmSignupForm.css b/src/forms/ConfirmSignupForm/ConfirmSignupForm.css new file mode 100644 index 000000000..c75f298f7 --- /dev/null +++ b/src/forms/ConfirmSignupForm/ConfirmSignupForm.css @@ -0,0 +1,54 @@ +@import '../../marketplace.css'; + +.root { + @apply --marketplaceModalFormRootStyles; + + justify-content: flex-start; + + @media (--viewportMedium) { + justify-content: space-between; + } +} + +.name { + display: flex; + justify-content: space-between; + margin-top: 24px; + + @media (--viewportMedium) { + margin-top: 32px; + } +} + +.firstNameRoot { + width: calc(34% - 9px); +} + +.lastNameRoot { + width: calc(66% - 9px); +} + +.password { + @apply --marketplaceModalPasswordMargins; +} + +.bottomWrapper { + @apply --marketplaceModalBottomWrapper; +} + +.bottomWrapperText { + @apply --marketplaceModalBottomWrapperText; +} + +.termsText { + @apply --marketplaceModalHelperText; +} + +.termsLink { + @apply --marketplaceModalHelperLink; + + &:hover { + text-decoration: underline; + cursor: pointer; + } +} diff --git a/src/forms/ConfirmSignupForm/ConfirmSignupForm.js b/src/forms/ConfirmSignupForm/ConfirmSignupForm.js new file mode 100644 index 000000000..11411bf62 --- /dev/null +++ b/src/forms/ConfirmSignupForm/ConfirmSignupForm.js @@ -0,0 +1,174 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { compose } from 'redux'; +import { FormattedMessage, injectIntl, intlShape } from '../../util/reactIntl'; +import { Form as FinalForm } from 'react-final-form'; +import classNames from 'classnames'; +import * as validators from '../../util/validators'; +import { Form, PrimaryButton, FieldTextInput, NamedRedirect } from '../../components'; + +import css from './ConfirmSignupForm.css'; + +const KEY_CODE_ENTER = 13; + +const ConfirmSignupFormComponent = props => ( + { + const { + rootClassName, + className, + formId, + handleSubmit, + inProgress, + invalid, + intl, + onOpenTermsOfService, + authInfo, + } = fieldRenderProps; + + // If we don't have authentication information from idp provider, redirect user back to normal signup page + if (!authInfo) { + return ; + } + + // email + const emailLabel = intl.formatMessage({ + id: 'ConfirmSignupForm.emailLabel', + }); + const emailPlaceholder = intl.formatMessage({ + id: 'ConfirmSignupForm.emailPlaceholder', + }); + const emailRequiredMessage = intl.formatMessage({ + id: 'ConfirmSignupForm.emailRequired', + }); + const emailRequired = validators.required(emailRequiredMessage); + const emailInvalidMessage = intl.formatMessage({ + id: 'ConfirmSignupForm.emailInvalid', + }); + const emailValid = validators.emailFormatValid(emailInvalidMessage); + + // firstName + const firstNameLabel = intl.formatMessage({ + id: 'ConfirmSignupForm.firstNameLabel', + }); + const firstNamePlaceholder = intl.formatMessage({ + id: 'ConfirmSignupForm.firstNamePlaceholder', + }); + const firstNameRequiredMessage = intl.formatMessage({ + id: 'ConfirmSignupForm.firstNameRequired', + }); + const firstNameRequired = validators.required(firstNameRequiredMessage); + + // lastName + const lastNameLabel = intl.formatMessage({ + id: 'ConfirmSignupForm.lastNameLabel', + }); + const lastNamePlaceholder = intl.formatMessage({ + id: 'ConfirmSignupForm.lastNamePlaceholder', + }); + const lastNameRequiredMessage = intl.formatMessage({ + id: 'ConfirmSignupForm.lastNameRequired', + }); + const lastNameRequired = validators.required(lastNameRequiredMessage); + + const classes = classNames(rootClassName || css.root, className); + const submitInProgress = inProgress; + const submitDisabled = invalid || submitInProgress; + + const handleTermsKeyUp = e => { + // Allow click action with keyboard like with normal links + if (e.keyCode === KEY_CODE_ENTER) { + onOpenTermsOfService(); + } + }; + const termsLink = ( + + + + ); + + // Initial values from idp provider + const { email, firstName, lastName, idpId } = authInfo; + + return ( +
+
+ +
+ + +
+
+ +
+

+ + + +

+ + + +
+
+ ); + }} + /> +); + +ConfirmSignupFormComponent.defaultProps = { inProgress: false }; + +const { bool, func } = PropTypes; + +ConfirmSignupFormComponent.propTypes = { + inProgress: bool, + + onOpenTermsOfService: func.isRequired, + + // from injectIntl + intl: intlShape.isRequired, +}; + +const ConfirmSignupForm = compose(injectIntl)(ConfirmSignupFormComponent); +ConfirmSignupForm.displayName = 'ConfirmSignupForm'; + +export default ConfirmSignupForm; diff --git a/src/forms/index.js b/src/forms/index.js index 2d53d99aa..ed423228b 100644 --- a/src/forms/index.js +++ b/src/forms/index.js @@ -1,5 +1,6 @@ export { default as BookingDatesForm } from './BookingDatesForm/BookingDatesForm'; export { default as ContactDetailsForm } from './ContactDetailsForm/ContactDetailsForm'; +export { default as ConfirmSignupForm } from './ConfirmSignupForm/ConfirmSignupForm'; export { default as EditListingAvailabilityForm } from './EditListingAvailabilityForm/EditListingAvailabilityForm'; export { default as EditListingDescriptionForm } from './EditListingDescriptionForm/EditListingDescriptionForm'; export { default as EditListingFeaturesForm } from './EditListingFeaturesForm/EditListingFeaturesForm'; diff --git a/src/translations/en.json b/src/translations/en.json index ee1cd46f4..1866b8b66 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -105,6 +105,19 @@ "CheckoutPage.speculateFailedMessage": "Oops, something went wrong. Please refresh the page and try again.", "CheckoutPage.speculateTransactionError": "Failed to fetch breakdown information.", "CheckoutPage.title": "Book {listingTitle}", + "ConfirmSignupForm.emailInvalid": "A valid email address is required", + "ConfirmSignupForm.emailLabel": "Email", + "ConfirmSignupForm.emailPlaceholder": "john.doe@example.com", + "ConfirmSignupForm.emailRequired": "You need to add an email.", + "ConfirmSignupForm.firstNameLabel": "First name", + "ConfirmSignupForm.firstNamePlaceholder": "John", + "ConfirmSignupForm.firstNameRequired": "You need to add a first name.", + "ConfirmSignupForm.lastNameLabel": "Last name", + "ConfirmSignupForm.lastNamePlaceholder": "Doe", + "ConfirmSignupForm.lastNameRequired": "You need to add a last name.", + "ConfirmSignupForm.signUp": "Continue with {idp}", + "ConfirmSignupForm.termsAndConditionsAcceptText": "By signing up you accept the {termsLink}", + "ConfirmSignupForm.termsAndConditionsLinkText": "terms and conditions", "ContactDetailsForm.confirmChangesInfo": "To change your email address, please enter your current password.", "ContactDetailsForm.confirmChangesTitle": "Confirm your changes", "ContactDetailsForm.emailInvalid": "A valid email address is required", @@ -919,7 +932,7 @@ "StripeConnectAccountForm.createStripeAccountFailedWithStripeError": "Whoops, something went wrong. Stripe returned an error message: \"{stripeMessage}\"", "StripeConnectAccountForm.individualAccount": "I'm an individual", "StripeConnectAccountForm.createStripeAccountLinkFailed": "Whoops, something went wrong. Please contact your marketplace administrators.", - "StripeConnectAccountForm.createStripeAccountLinkFailedWithStripeError": "Whoops, something went wrong. Stripe returned an error message: \"{stripeMessage}\"", + "StripeConnectAccountForm.createStripeAccountLinkFailedWithStripeError": "Whoops, something went wrong. Stripe returned an error message: \"{stripeMessage}\"", "StripeConnectAccountForm.loadingStripeAccountData": "Fetching payout details…", "StripeConnectAccountForm.stripeToSText": "By saving details, you agree to the {stripeConnectedAccountTermsLink}", "StripeConnectAccountForm.stripeConnectedAccountTermsLink": "Stripe Connected Account Agreement", From f65dc7e5c59c2546ad0d8f7607b043a271f7df78 Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 11:15:16 +0300 Subject: [PATCH 15/29] routeConfiguration: add route for ConfirmPage --- src/routeConfiguration.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/routeConfiguration.js b/src/routeConfiguration.js index edd60229a..8251c6e03 100644 --- a/src/routeConfiguration.js +++ b/src/routeConfiguration.js @@ -175,6 +175,11 @@ const routeConfiguration = () => { name: 'SignupPage', component: props => , }, + { + path: '/confirm', + name: 'ConfirmPage', + component: props => , + }, { path: '/recover-password', name: 'PasswordRecoveryPage', From 0fdc46a592a3fdb3b1820715ec72fd6fe940a9c6 Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 11:19:37 +0300 Subject: [PATCH 16/29] Add Facebook app credentials to env-template and config.js --- .env-template | 5 +++++ src/config.js | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.env-template b/.env-template index 469225c32..a48fa19db 100644 --- a/.env-template +++ b/.env-template @@ -21,6 +21,11 @@ SHARETRIBE_SDK_CLIENT_SECRET= REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY=USD REACT_APP_CANONICAL_ROOT_URL=http://localhost:3000 +# Social logins && SSO +# If the app or client id is not set the auhtentication option is not shown in FTW +REACT_APP_FACEBOOK_APP_ID= +FACEBOOK_APP_SECRET= + # This is overwritten by configuration in .env.development and # .env.test. In production deployments use env variable and set it to # 'production' diff --git a/src/config.js b/src/config.js index c79cc15cc..0233fcae6 100644 --- a/src/config.js +++ b/src/config.js @@ -101,11 +101,14 @@ const siteInstagramPage = null; // Facebook page is used in SEO schema (http://schema.org/Organization) const siteFacebookPage = 'https://www.facebook.com/Sharetribe/'; +// Social logins & SSO + +// Note: Facebook app id is also used for tracking: // Facebook counts shares with app or page associated by this id // Currently it is unset, but you can read more about fb:app_id from // https://developers.facebook.com/docs/sharing/webmasters#basic // You should create one to track social sharing in Facebook -const facebookAppId = null; +const facebookAppId = process.env.REACT_APP_FACEBOOK_APP_ID; const maps = { mapboxAccessToken: process.env.REACT_APP_MAPBOX_ACCESS_TOKEN, From 48cd2d382b84266d48abc3a78a0f51328851ef62 Mon Sep 17 00:00:00 2001 From: Jenni Laakso Date: Tue, 22 Sep 2020 11:23:27 +0300 Subject: [PATCH 17/29] Button: add new button type for SocialLoginButton --- src/components/Button/Button.css | 35 ++++++++++++++++++++++++++++++++ src/components/Button/Button.js | 7 +++++++ src/components/index.js | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/components/Button/Button.css b/src/components/Button/Button.css index 1cb34644d..422a4fb04 100644 --- a/src/components/Button/Button.css +++ b/src/components/Button/Button.css @@ -106,3 +106,38 @@ .secondaryButton .checkmark { stroke: var(--matterColorAnti); } + +/* Social logins && SSO buttons */ + +.socialButtonRoot { + @apply --marketplaceButtonStyles; + min-height: 44px; + background-color: var(--matterColorLight); + color: var(--matterColorDark); + font-weight: var(--fontWeightRegular); + + border: 1px solid var(--matterColorNegative); + border-radius: 6px; + margin: 0 auto; + margin-bottom: 8px; + width: 100%; + text-align: left; + + /* We need to add this to position the icon inside button */ + position: relative; + + @media (--viewportMedium) { + padding: 0; + } + + &:hover, + &:focus { + background-color: var(--matterColorLight); + } + &:disabled { + background-color: var(--matterColorNegative); + } +} + +.socialButton { +} diff --git a/src/components/Button/Button.js b/src/components/Button/Button.js index f934c25af..b0de63bce 100644 --- a/src/components/Button/Button.js +++ b/src/components/Button/Button.js @@ -100,3 +100,10 @@ export const InlineTextButton = props => { return