diff --git a/.env-template b/.env-template new file mode 100644 index 00000000..b639a6b6 --- /dev/null +++ b/.env-template @@ -0,0 +1,32 @@ + +# Mandatory configuration +# +REACT_APP_SHARETRIBE_SDK_CLIENT_ID= +REACT_APP_GOOGLE_MAPS_API_KEY= +REACT_APP_STRIPE_PUBLISHABLE_KEY= + +# Defaults +# + +REACT_APP_SHARETRIBE_SDK_BASE_URL=http://localhost:8088 + +REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY=USD +REACT_APP_CANONICAL_ROOT_URL=http://localhost:3000 + +# This is overwritten by configuration in .env.development and +# .env.test. In production deployments use env variable and set it to +# 'production' +REACT_APP_ENV=production + + +# Options. Uncomment and set to test. +# + +# REACT_APP_SHARETRIBE_USING_SSL=true +# SERVER_SHARETRIBE_TRUST_PROXY=true +# REACT_APP_PUBLIC_SENTRY_DSN=change-me +# SERVER_SENTRY_DSN=change-me +# REACT_APP_CSP=report +# BASIC_AUTH_USERNAME=sharetribe +# BASIC_AUTH_PASSWORD=secret +# REACT_APP_GOOGLE_ANALYTICS_ID=change-me \ No newline at end of file diff --git a/.env.development b/.env.development index cff1ca7b..5a86b627 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ -NODE_ENV=development REACT_APP_ENV=development + diff --git a/.env.test b/.env.test index d8e17a78..21dd1f79 100644 --- a/.env.test +++ b/.env.test @@ -1,3 +1,4 @@ -NODE_ENV=test REACT_APP_ENV=test REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY=USD +REACT_APP_CANONICAL_ROOT_URL=http://localhost:3000 + diff --git a/.gitignore b/.gitignore index 8a687143..f25c23c5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ build .DS_Store .env npm-debug.log -/.env.development.local +yarn-error.log diff --git a/README.md b/README.md index 494e3eee..998a6602 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,16 @@ Clone this repository and install dependencies: ## Development +**First, you need to configure Client Id, Google Maps API key and +Stripe publishable API key via [environment +variables](./docs/env.md).** The quickest way to do this is to copy +`.env-template` -> `.env`, and edit the file to set the three mandatory +configuration values. + To develop the application and to see changes live, start the frontend development server: yarn run dev -**You also need to [configure Google Maps api key](./docs/google-maps.md)** - **Known issues:** * Adding/changing `import`s may not be synced properly with ESLint. You may see an error @@ -57,13 +61,6 @@ To start the test watcher, run See more in the [testing documentation](docs/testing.md). -## Deployment - -The `master` branch is automatically deployed to Heroku. If you want to deploy a feature branch, -setup the `sharetribe-starter-app` Heroku app and push the feature branch: - - git push heroku my-feature-branch:master - ## License This project is licensed under the terms of the Apache-2.0 license. diff --git a/docs/colors-and-icons.md b/docs/colors-and-icons.md index 3615550a..7cd452d4 100644 --- a/docs/colors-and-icons.md +++ b/docs/colors-and-icons.md @@ -12,7 +12,9 @@ change the color variables: --marketplaceColorLight: #ff4c38; --marketplaceColorDark: #8c291e; ``` -N.B. search also from files color code `c0392b`. Some icons and background-images with data-URL are using it directly. + +N.B. search also from files color code `c0392b`. Some icons and background-images with data-URL are +using it directly. ## Icons diff --git a/docs/customization-checklist.md b/docs/customization-checklist.md index 49f05649..824b497e 100644 --- a/docs/customization-checklist.md +++ b/docs/customization-checklist.md @@ -1,36 +1,42 @@ - # Customization checklist Some generic things to update and check when starting to customize Starter app. -- [Marketplace colors](colors-and-icons.md#colors) -- [Generate app icons](colors-and-icons.md#icons) -- [Update translations](../src/translations/en.json) or [change the language](i18n.md) -- [LandingPage](../src/containers/LandingPage/LandingPage.js): update and create branded sections -- [Footer](../src/components/Footer/Footer.js) -- [AboutPage](../src/containers/AboutPage/AboutPage.js) -- [Terms of Service](terms-of-service-and-privacy-policy.md#terms-of-service) -- [Privacy Policy](terms-of-service-and-privacy-policy.md#privacy-policy) -- [Social media sharing graphics](../src/components/Page/Page.js); -- [Logo](../src/components/Logo/Logo.js) Change and check that it works on Topbar and Footer -- [Logo in CheckoutPage](../src/containers/CheckoutPage/LogoIcon.js) -- [Default background image](../src/assets/background-1440.jpg) -- [Maps Marker icon](../src/components/Map/Map/images/marker-32x32.png) -- [Config: update environment variables](../src/config.js) -- [Config: siteTitle](../src/config.js) for page schema (SEO) -- [Config: marketplace address](../src/config.js): contact details also improve SEO -- [Config: social media pages](../src/config.js) -- [Marketplace custom config](../src/marketplace-custom-config.js) -- Update [ListingPage](../src/containers/ListingPage/ListingPage.js) to show extended data (aka publicData attribute) -- Update [EditListingWizard](../src/components/EditListingWizard/EditListingWizard.js) and panels to add extended data -- Update [SearchPage](../src/containers/SearchPage/SearchPage.js) to filter with extended data -- Update [routeConfiguration](../src/routeConfiguration) if needed -- Update [Email templates](https://github.com/sharetribe/sharetribe-starter-app/tree/master/ext/mail-templates), all of them: .html, subject.txt, and text.txt -- Update [config: bookingUnitType](../src/config.js) if needed -- If `line-item/units` is used, add quantity handling -to [BookingDatesForm](../src/containers/BookingDatesForm/BookingDatesForm.js), [ListingPage](../src/containers/ListingPage/ListingPage.js), [CheckoutPage](../src/containers/CheckoutPage/CheckoutPage.js) +* [Marketplace colors](colors-and-icons.md#colors) +* [Generate app icons](colors-and-icons.md#icons) +* [Update translations](../src/translations/en.json) or [change the language](i18n.md) +* [LandingPage](../src/containers/LandingPage/LandingPage.js): update and create branded sections +* [Footer](../src/components/Footer/Footer.js) +* [AboutPage](../src/containers/AboutPage/AboutPage.js) +* [Terms of Service](terms-of-service-and-privacy-policy.md#terms-of-service) +* [Privacy Policy](terms-of-service-and-privacy-policy.md#privacy-policy) +* [Social media sharing graphics](../src/components/Page/Page.js); +* [Logo](../src/components/Logo/Logo.js) Change and check that it works on Topbar and Footer +* [Logo in CheckoutPage](../src/containers/CheckoutPage/LogoIcon.js) +* [Default background image](../src/assets/background-1440.jpg) +* [Maps Marker icon](../src/components/Map/Map/images/marker-32x32.png) +* [Config: update environment variables](../src/config.js) +* [Config: siteTitle](../src/config.js) for page schema (SEO) +* [Config: marketplace address](../src/config.js): contact details also improve SEO +* [Config: social media pages](../src/config.js) +* [Marketplace custom config](../src/marketplace-custom-config.js) +* Update [ListingPage](../src/containers/ListingPage/ListingPage.js) to show extended data (aka + publicData attribute) +* Update [EditListingWizard](../src/components/EditListingWizard/EditListingWizard.js) and panels to + add extended data +* Update [SearchPage](../src/containers/SearchPage/SearchPage.js) to filter with extended data +* Update [routeConfiguration](../src/routeConfiguration) if needed +* Update + [Email templates](https://github.com/sharetribe/sharetribe-starter-app/tree/master/ext/mail-templates), + all of them: .html, subject.txt, and text.txt +* Update [config: bookingUnitType](../src/config.js) if needed +* If `line-item/units` is used, add quantity handling to + [BookingDatesForm](../src/containers/BookingDatesForm/BookingDatesForm.js), + [ListingPage](../src/containers/ListingPage/ListingPage.js), + [CheckoutPage](../src/containers/CheckoutPage/CheckoutPage.js) ## What else -- Do you need more [static pages](static-pages.md)? -- Changes to existing pages -- Changes to transaction process (API + Web app) + +* Do you need more [static pages](static-pages.md)? +* Changes to existing pages +* Changes to transaction process (API + Web app) diff --git a/docs/env.md b/docs/env.md new file mode 100644 index 00000000..33e28982 --- /dev/null +++ b/docs/env.md @@ -0,0 +1,41 @@ +# Environment configuration variables + +The following configuration variables can be set to control the Starter App behaviour. Most of them +have defaults that work for development environment. For production deploys most should be set. + +| Variable | Description | +| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| REACT_APP_GOOGLE_MAPS_API_KEY | See: [Google Maps API key](./google-maps.md) | +| REACT_APP_SHARETRIBE_SDK_CLIENT_ID | Client ID (API key). You will get this from the Sharetribe team. | +| REACT_APP_STRIPE_PUBLISHABLE_KEY | Stripe publishable API key for generating tokens with Stripe API. Use test key (prefix pk*test*) for development. | +| REACT_APP_SHARETRIBE_SDK_BASE_URL | The base url to access the Sharetribe Flex Marketplace API. | +| REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY | The currency used in the Marketplace as ISO 4217 currency code. For example: USD, EUR, CAD, AUD, etc. | +| REACT_APP_CANONICAL_ROOT_URL | Canonical root url of the marketplace. Needed for social media sharing and SEO optimization. | +| NODE_ENV | Node env. Use 'development' for development and 'production' for production. | +| PORT | Port for server to accept connections. | +| REACT_APP_ENV | Env for client side. Use the same value as with NODE_ENV. | +| REACT_APP_SHARETRIBE_USING_SSL | Redirect HTTP to HTTPS? | +| SERVER_SHARETRIBE_TRUST_PROXY | Set when running the app behind a reverse proxy, e.g. in Heroku. | +| REACT_APP_PUBLIC_SENTRY_DSN | See: [Error logging with Sentry](./sentry.md) | +| SERVER_SENTRY_DSN | See: [Error logging with Sentry](./sentry.md) | +| REACT_APP_CSP | See: [Content Security Policy (CSP)](./content-security-policy.md) | +| BASIC_AUTH_USERNAME | Set to enable HTTP Basic Auth | +| BASIC_AUTH_PASSWORD | Set to enable HTTP Basic Auth | +| REACT_APP_GOOGLE_ANALYTICS_ID | See: [Google Analytics](./analytics.md) | + +## Defining configuration + +When the Starter App is started locally with `yarn run dev` or `yarn run dev-server`, you can set +environment variables by using the (gitignored) `.env` file. The repository contains a template file +`.env-template` with default configuration. Just copy that as `.env` and edit as necessary. + +In production, it's recommended that you set the configuration via env variables and do not deploy +an .env file. The client application will only be packaged with env variables that start with +REACT_APP. This way server secrets don't end up in client bundles. + +**With deploys note that the configuration options are bundled in the +client package at build time.** The configuration of the build +environment must match run environment for things to work +consistently. To apply changes to configuration values client bundle +must be rebuilt. Just restarting the server is not enough. + diff --git a/docs/google-maps.md b/docs/google-maps.md index 8104697b..1c9814d2 100644 --- a/docs/google-maps.md +++ b/docs/google-maps.md @@ -20,8 +20,7 @@ section of the Places library documentation to enable using the Google Places AP ## Setup the application to use the API key The application uses the `REACT_APP_GOOGLE_MAPS_API_KEY` environment variable for the key value. For -local development, you can add the variable in the Gitignored `.env.development.local` file in the -project root: +local development, you can add the variable in the Gitignored `.env` file in the project root: ``` REACT_APP_GOOGLE_MAPS_API_KEY=my-key-here diff --git a/docs/sentry.md b/docs/sentry.md index 936284ff..dae4509f 100644 --- a/docs/sentry.md +++ b/docs/sentry.md @@ -24,16 +24,15 @@ the logging logic in general. ## Source map retrieval -By default Sentry fetches the source maps for minified javascript files. However, this might now work -in case some sort of authentication, e.g. basic auth, is required to reach your application. To +By default Sentry fetches the source maps for minified javascript files. However, this might now +work in case some sort of authentication, e.g. basic auth, is required to reach your application. To circumvent this a security token can be configured to Sentry. Sentry then adds this token to the -source maps request header so that the server hosting Starter App can then be configured to let those -requests access the source maps. +source maps request header so that the server hosting Starter App can then be configured to let +those requests access the source maps. For basic auth the security token can be configured as follows: -In *Project > Settings > General > Client Security* set the following values +In _Project > Settings > General > Client Security_ set the following values * **Security token** - `Basic ` * **Security token header** - `Authorization` - diff --git a/package.json b/package.json index 3dbf651d..51d916c2 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "test-ci": "sharetribe-scripts test --env=jsdom --runInBand", "eject": "sharetribe-scripts eject", "start": "node server/index.js", - "dev-server": "export REACT_APP_ENV=development REACT_APP_CANONICAL_ROOT_URL=http://localhost:4000&&yarn run build&&nodemon --watch server server/index.js", + "dev-server": "export NODE_ENV=development PORT=4000 REACT_APP_CANONICAL_ROOT_URL=http://localhost:4000&&yarn run build&&nodemon --watch server server/index.js", "heroku-prebuild": "./heroku-copy-ssh-key-from-env.sh", "heroku-postbuild": "yarn run build", "audit": "nsp check --preprocessor yarn" diff --git a/server/csp.js b/server/csp.js index 278512e3..6d7d40f8 100644 --- a/server/csp.js +++ b/server/csp.js @@ -1,5 +1,4 @@ const helmet = require('helmet'); -const { config } = require('./importer'); const dev = process.env.REACT_APP_ENV === 'development'; const googleAnalyticsEnabled = !!process.env.REACT_APP_GOOGLE_ANALYTICS_ID; @@ -21,7 +20,7 @@ module.exports = (reportUri, enforceSsl, reportOnly) => { const self = "'self'"; const inline = "'unsafe-inline'"; const data = 'data:'; - const sharetribeApi = config.sdk.baseUrl; + const sharetribeApi = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL; const sharetribeAssets = 'https://assets-sharetribecom.sharetribe.com/'; const imgix = '*.imgix.net/'; const loremPixel = 'https://lorempixel.com/'; diff --git a/server/env.js b/server/env.js new file mode 100644 index 00000000..9fe75209 --- /dev/null +++ b/server/env.js @@ -0,0 +1,41 @@ +/** + Implements .env file loading that mimicks the way create-react-app + does it. We want this to get consistent configuration handling + between client and node server. +*/ + +const fs = require('fs'); + +const NODE_ENV = process.env.NODE_ENV; + +if (!NODE_ENV) { + throw new Error('The NODE_ENV environment variable is required but was not specified.'); +} + +// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use +var dotenvFiles = [ + `.env.${NODE_ENV}.local`, + `.env.${NODE_ENV}`, + // Don't include `.env.local` for `test` environment + // since normally you expect tests to produce the same + // results for everyone + NODE_ENV !== 'test' && `env.local`, + '.env', +].filter(Boolean); + +const configureEnv = () => { + // Load environment variables from .env* files. Suppress warnings using silent + // if this file is missing. dotenv will never modify any environment variables + // that have already been set. + // https://github.com/motdotla/dotenv + dotenvFiles.forEach(dotenvFile => { + if (fs.existsSync(dotenvFile)) { + console.log('Loading env from file:' + dotenvFile); + require('dotenv').config({ path: dotenvFile }); + } + }); +}; + +module.exports = { + configureEnv: configureEnv, +}; diff --git a/server/importer.js b/server/importer.js index f572bcac..83eb744f 100644 --- a/server/importer.js +++ b/server/importer.js @@ -17,5 +17,4 @@ module.exports = { matchPathname: mainJs.matchPathname, configureStore: mainJs.configureStore, routeConfiguration: mainJs.routeConfiguration, - config: mainJs.config, }; diff --git a/server/index.js b/server/index.js index 44c9da50..bd9d9fac 100644 --- a/server/index.js +++ b/server/index.js @@ -16,6 +16,9 @@ // This enables nice stacktraces from the minified production bundle require('source-map-support').install(); +// Configure process.env with .env.* files +require('./env').configureEnv(); + const express = require('express'); const helmet = require('helmet'); const compression = require('compression'); @@ -35,12 +38,11 @@ const { sitemapStructure } = require('./sitemap'); const csp = require('./csp'); const buildPath = path.resolve(__dirname, '..', 'build'); -const env = process.env.REACT_APP_ENV || 'production'; +const env = process.env.REACT_APP_ENV; const dev = process.env.REACT_APP_ENV === 'development'; -const PORT = process.env.PORT || 4000; -const CLIENT_ID = - process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID || '08ec69f6-d37e-414d-83eb-324e94afddf0'; -const BASE_URL = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL || 'http://localhost:8088'; +const PORT = parseInt(process.env.PORT, 10); +const CLIENT_ID = process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID; +const BASE_URL = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL; const USING_SSL = process.env.REACT_APP_SHARETRIBE_USING_SSL === 'true'; const TRUST_PROXY = process.env.SERVER_SHARETRIBE_TRUST_PROXY || null; const CSP = process.env.REACT_APP_CSP; diff --git a/server/sitemap.js b/server/sitemap.js index 4b9825c7..e3d10139 100644 --- a/server/sitemap.js +++ b/server/sitemap.js @@ -1,9 +1,8 @@ const path = require('path'); const moment = require('moment'); -const { config } = require('./importer'); const buildPath = path.resolve(__dirname, '..', 'build'); -const PORT = process.env.PORT || 4000; +const PORT = parseInt(process.env.PORT, 10); const USING_SSL = process.env.REACT_APP_SHARETRIBE_USING_SSL === 'true'; /** @@ -44,17 +43,17 @@ const port = rootURL => { }; /** - * Return a structure for sitemap.xml and robots.txt to be - * used by the express-sitemap library. Uses the canonical - * URL value from config for domain and port information. + * Return a structure for sitemap.xml and robots.txt to be used by the + * express-sitemap library. Uses the canonical URL value from env + * config for domain and port information. */ exports.sitemapStructure = () => { const now = moment().format('YYYY-MM-DD'); return { - url: domain(config.canonicalRootURL), + url: domain(process.env.REACT_APP_CANONICAL_ROOT_URL), http: USING_SSL ? 'https' : 'http', - port: port(config.canonicalRootURL), + port: port(process.env.REACT_APP_CANONICAL_ROOT_URL), sitemap: path.join(buildPath, 'static', 'sitemap.xml'), robots: path.join(buildPath, 'robots.txt'), sitemapSubmission: '/static/sitemap.xml', diff --git a/src/config.js b/src/config.js index 22c6fc03..0d0972af 100644 --- a/src/config.js +++ b/src/config.js @@ -1,6 +1,6 @@ import * as custom from './marketplace-custom-config.js'; -const env = process.env.REACT_APP_ENV || 'production'; +const env = process.env.REACT_APP_ENV; const dev = process.env.REACT_APP_ENV === 'development'; // If you want to change the language, remember to also change the @@ -32,11 +32,10 @@ const bookingUnitType = 'line-item/night'; // script, react-scripts (and the sharetribe-scripts fork of // react-scripts) require using the REACT_APP_ prefix to avoid // exposing server secrets to the client side. -const sdkClientId = - process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID || '08ec69f6-d37e-414d-83eb-324e94afddf0'; -const sdkBaseUrl = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL || 'http://localhost:8088'; +const sdkClientId = process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID; +const sdkBaseUrl = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL; -const currency = process.env.REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY || 'USD'; +const currency = process.env.REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY; // Sentry DSN (Data Source Name), a client key for authenticating calls to Sentry const sentryDsn = process.env.REACT_APP_PUBLIC_SENTRY_DSN; @@ -57,8 +56,7 @@ const currencyConfig = { maximumFractionDigits: 2, }; -const stripePublishableKey = - process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY || 'pk_test_FWtdKPZvtrj37t45dIomssBI'; +const stripePublishableKey = process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY; // Stripe only supports payments in certain countries, see full list // at https://stripe.com/global @@ -221,7 +219,7 @@ const postalCode = '00100'; const streetAddress = 'Bulevardi 14'; // Canonical root url is needed in social media sharing and SEO optimization purposes. -const canonicalRootURL = process.env.REACT_APP_CANONICAL_ROOT_URL || 'http://localhost:3000'; +const canonicalRootURL = process.env.REACT_APP_CANONICAL_ROOT_URL; // Site title is needed in meta tags (bots and social media sharing reads those) const siteTitle = 'Saunatime';