Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update v9.2.0 (update from upstream aka FTW-daily v8.5.0) #147

Merged
merged 18 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,29 @@ way to update this template, but currently, we follow a pattern:

## Upcoming version 2022-XX-XX

## [v9.2.0] 2022-05-16

- Add support for hosted translations. (PR made in upstream repo: FTW-daily)

- [delete] Remove old unused translation keys
[#146](https://github.com/sharetribe/ftw-product/pull/146)

### Updates from upstream (FTW-daily v8.5.0)

- [add] Add support for hosted translations.

- This PR fetches "content/translation.json" from a new Asset Delivery API. The file is editable
through the Flex Console.
- It also adds all the missing translation keys to existing non-English translation files. This
means that those files might now include messages in English.

[#1510](https://github.com/sharetribe/ftw-daily/pull/1510)

- [delete] Remove old unused translation keys.
[#1511](https://github.com/sharetribe/ftw-daily/pull/1511)

[v9.2.0]: https://github.com/sharetribe/ftw-daily/compare/v9.1.2...v9.2.0

## [v9.1.2] 2022-03-16

- [fix] Process graph had an error: provider notification about dispute being canceled was not sent
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "app",
"version": "9.1.2",
"version": "9.2.0",
"private": true,
"license": "Apache-2.0",
"dependencies": {
Expand Down Expand Up @@ -61,7 +61,7 @@
"redux": "^4.1.2",
"redux-thunk": "^2.4.1",
"seedrandom": "^3.0.5",
"sharetribe-flex-sdk": "^1.15.0",
"sharetribe-flex-sdk": "^1.17.0",
"sharetribe-scripts": "5.0.1",
"smoothscroll-polyfill": "^0.4.0",
"source-map-support": "^0.5.21",
Expand Down
6 changes: 6 additions & 0 deletions server/csp.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ const data = 'data:';
const blob = 'blob:';
const devImagesMaybe = dev ? ['*.localhost:8000'] : [];
const baseUrl = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL || 'https://flex-api.sharetribe.com';
// Asset Delivery API is using a different domain than other Flex APIs
// cdn.st-api.com
// If assetCdnBaseUrl is used to initialize SDK (for proxy purposes), then that URL needs to be in CSP
const assetCdnBaseUrl = process.env.REACT_APP_SHARETRIBE_SDK_ASSET_CDN_BASE_URL;

// Default CSP whitelist.
//
Expand All @@ -20,6 +24,8 @@ const defaultDirectives = {
connectSrc: [
self,
baseUrl,
assetCdnBaseUrl,
'*.st-api.com',
'maps.googleapis.com',
'*.tiles.mapbox.com',
'api.mapbox.com',
Expand Down
17 changes: 14 additions & 3 deletions server/dataLoader.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const url = require('url');
const log = require('./log');

exports.loadData = function(requestUrl, sdk, matchPathname, configureStore, routeConfiguration) {
exports.loadData = function(requestUrl, sdk, appInfo) {
const { matchPathname, configureStore, routeConfiguration, config, fetchAppAssets } = appInfo;
const { pathname, query } = url.parse(requestUrl);
const matchedRoutes = matchPathname(pathname, routeConfiguration());

let translations = {};
const store = configureStore({}, sdk);

const dataLoadingCalls = matchedRoutes.reduce((calls, match) => {
Expand All @@ -15,9 +17,18 @@ exports.loadData = function(requestUrl, sdk, matchPathname, configureStore, rout
return calls;
}, []);

return Promise.all(dataLoadingCalls)
// First fetch app-wide assets
// Then make loadData calls
// And return object containing preloaded state and translations
// This order supports other asset (in the future) that should be fetched before data calls.
return store
.dispatch(fetchAppAssets(config.appCdnAssets))
.then(fetchedAssets => {
translations = fetchedAssets?.translations?.data || {};
return Promise.all(dataLoadingCalls);
})
.then(() => {
return store.getState();
return { preloadedState: store.getState(), translations };
})
.catch(e => {
log.error(e, 'server-side-data-load-failed');
Expand Down
11 changes: 7 additions & 4 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const dev = process.env.REACT_APP_ENV === 'development';
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 ASSET_CDN_BASE_URL = process.env.REACT_APP_SHARETRIBE_SDK_ASSET_CDN_BASE_URL;
const TRANSIT_VERBOSE = process.env.REACT_APP_SHARETRIBE_SDK_TRANSIT_VERBOSE === 'true';
const USING_SSL = process.env.REACT_APP_SHARETRIBE_USING_SSL === 'true';
const TRUST_PROXY = process.env.SERVER_SHARETRIBE_TRUST_PROXY || null;
Expand Down Expand Up @@ -199,6 +200,7 @@ app.get('*', (req, res) => {
});

const baseUrl = BASE_URL ? { baseUrl: BASE_URL } : {};
const assetCdnBaseUrl = ASSET_CDN_BASE_URL ? { assetCdnBaseUrl: ASSET_CDN_BASE_URL } : {};

const sdk = sharetribeSdk.createInstance({
transitVerbose: TRANSIT_VERBOSE,
Expand All @@ -208,6 +210,7 @@ app.get('*', (req, res) => {
tokenStore,
typeHandlers: sdkUtils.typeHandlers,
...baseUrl,
...assetCdnBaseUrl,
});

// Until we have a better plan for caching dynamic content and we
Expand All @@ -221,12 +224,12 @@ app.get('*', (req, res) => {

// Server-side entrypoint provides us the functions for server-side data loading and rendering
const nodeEntrypoint = nodeExtractor.requireEntrypoint();
const { default: renderApp, matchPathname, configureStore, routeConfiguration } = nodeEntrypoint;
const { default: renderApp, ...appInfo } = nodeEntrypoint;

dataLoader
.loadData(req.url, sdk, matchPathname, configureStore, routeConfiguration)
.then(preloadedState => {
const html = renderer.render(req.url, context, preloadedState, renderApp, webExtractor);
.loadData(req.url, sdk, appInfo)
.then(data => {
const html = renderer.render(req.url, context, data, renderApp, webExtractor);

if (dev) {
const debugData = {
Expand Down
13 changes: 11 additions & 2 deletions server/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,20 @@ const replacer = (key = null, value) => {
return types.replacer(key, cleanedValue);
};

exports.render = function(requestUrl, context, preloadedState, renderApp, webExtractor) {
exports.render = function(requestUrl, context, data, renderApp, webExtractor) {
const { preloadedState, translations } = data;

// Bind webExtractor as "this" for collectChunks call.
const collectWebChunks = webExtractor.collectChunks.bind(webExtractor);

const { head, body } = renderApp(requestUrl, context, preloadedState, collectWebChunks);
// Render the app with given route, preloaded state, hosted translations.
const { head, body } = renderApp(
requestUrl,
context,
preloadedState,
translations,
collectWebChunks
);

// Preloaded state needs to be passed for client side too.
// For security reasons we ensure that preloaded state is considered as a string
Expand Down
62 changes: 43 additions & 19 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import routeConfiguration from './routing/routeConfiguration';
import Routes from './routing/Routes';
import config from './config';

// Flex template application uses English translations as default.
// Flex template application uses English translations as default translations.
import defaultMessages from './translations/en.json';

// If you want to change the language, change the imports to match the wanted locale:
// If you want to change the language of default (fallback) translations,
// change the imports to match the wanted locale:
//
// 1) Change the language in the config.js file!
// 2) Import correct locale rules for Moment library
// 3) Use the `messagesInLocale` import to add the correct translation file.
Expand All @@ -49,6 +51,11 @@ const messagesInLocale = {};
const addMissingTranslations = (sourceLangTranslations, targetLangTranslations) => {
const sourceKeys = Object.keys(sourceLangTranslations);
const targetKeys = Object.keys(targetLangTranslations);

// if there's no translations defined for target language, return source translations
if (targetKeys.length === 0) {
return sourceLangTranslations;
}
const missingKeys = difference(sourceKeys, targetKeys);

const addMissingTranslation = (translations, missingKey) => ({
Expand All @@ -59,18 +66,15 @@ const addMissingTranslations = (sourceLangTranslations, targetLangTranslations)
return missingKeys.reduce(addMissingTranslation, targetLangTranslations);
};

const isDefaultLanguageInUse = config.locale === 'en';

const messages = isDefaultLanguageInUse
? defaultMessages
: addMissingTranslations(defaultMessages, messagesInLocale);

// Get default messages for a given locale.
//
// Note: Locale should not affect the tests. We ensure this by providing
// messages with the key as the value of each message and discard the value.
// { 'My.translationKey1': 'My.translationKey1', 'My.translationKey2': 'My.translationKey2' }
const isTestEnv = process.env.NODE_ENV === 'test';

// Locale should not affect the tests. We ensure this by providing
// messages with the key as the value of each message.
const testMessages = mapValues(messages, (val, key) => key);
const localeMessages = isTestEnv ? testMessages : messages;
const localeMessages = isTestEnv
? mapValues(defaultMessages, (val, key) => key)
: addMissingTranslations(defaultMessages, messagesInLocale);

const setupLocale = () => {
if (isTestEnv) {
Expand All @@ -86,10 +90,14 @@ const setupLocale = () => {
};

export const ClientApp = props => {
const { store } = props;
const { store, hostedTranslations = {} } = props;
setupLocale();
return (
<IntlProvider locale={config.locale} messages={localeMessages} textComponent="span">
<IntlProvider
locale={config.locale}
messages={{ ...localeMessages, ...hostedTranslations }}
textComponent="span"
>
<Provider store={store}>
<HelmetProvider>
<IncludeMapLibraryScripts />
Expand All @@ -107,11 +115,15 @@ const { any, string } = PropTypes;
ClientApp.propTypes = { store: any.isRequired };

export const ServerApp = props => {
const { url, context, helmetContext, store } = props;
const { url, context, helmetContext, store, hostedTranslations = {} } = props;
setupLocale();
HelmetProvider.canUseDOM = false;
return (
<IntlProvider locale={config.locale} messages={localeMessages} textComponent="span">
<IntlProvider
locale={config.locale}
messages={{ ...localeMessages, ...hostedTranslations }}
textComponent="span"
>
<Provider store={store}>
<HelmetProvider context={helmetContext}>
<IncludeMapLibraryScripts />
Expand All @@ -136,7 +148,13 @@ ServerApp.propTypes = { url: string.isRequired, context: any.isRequired, store:
* - {String} body: Rendered application body of the given route
* - {Object} head: Application head metadata from react-helmet
*/
export const renderApp = (url, serverContext, preloadedState, collectChunks) => {
export const renderApp = (
url,
serverContext,
preloadedState,
hostedTranslations,
collectChunks
) => {
// Don't pass an SDK instance since we're only rendering the
// component tree with the preloaded store state and components
// shouldn't do any SDK calls in the (server) rendering lifecycle.
Expand All @@ -148,7 +166,13 @@ export const renderApp = (url, serverContext, preloadedState, collectChunks) =>
// This is needed to figure out correct chunks/scripts to be included to server-rendered page.
// https://loadable-components.com/docs/server-side-rendering/#3-setup-chunkextractor-server-side
const WithChunks = collectChunks(
<ServerApp url={url} context={serverContext} helmetContext={helmetContext} store={store} />
<ServerApp
url={url}
context={serverContext}
helmetContext={helmetContext}
store={store}
hostedTranslations={hostedTranslations}
/>
);
const body = ReactDOMServer.renderToString(WithChunks);
const { helmet: head } = helmetContext;
Expand Down
10 changes: 10 additions & 0 deletions src/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import { subUnitDivisors, currencyConfiguration } from './currency-config';
const env = process.env.REACT_APP_ENV;
const dev = process.env.REACT_APP_ENV === 'development';

// CDN assets for the app. Configurable through Flex Console.
// Currently, only translation.json is available.
// Note: the path must match the path defined in Asset Delivery API
const appCdnAssets = {
translations: 'content/translations.json',
};

// If you want to change the language, remember to also change the
// locale data and the messages in the app.js file.
const locale = 'en';
Expand Down Expand Up @@ -83,6 +90,7 @@ const dayCountAvailableForBooking = 90;
// exposing server secrets to the client side.
const sdkClientId = process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID;
const sdkBaseUrl = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL;
const sdkAssetCdnBaseUrl = process.env.REACT_APP_SHARETRIBE_SDK_ASSET_CDN_BASE_URL;
const sdkTransitVerbose = process.env.REACT_APP_SHARETRIBE_SDK_TRANSIT_VERBOSE === 'true';

// Marketplace currency.
Expand Down Expand Up @@ -233,6 +241,7 @@ const maps = {
const config = {
env,
dev,
appCdnAssets,
locale,
transactionProcessAlias,
lineItemUnitType,
Expand All @@ -242,6 +251,7 @@ const config = {
sdk: {
clientId: sdkClientId,
baseUrl: sdkBaseUrl,
assetCdnBaseUrl: sdkAssetCdnBaseUrl,
transitVerbose: sdkTransitVerbose,
},
mainSearchType,
Expand Down
16 changes: 12 additions & 4 deletions src/containers/LandingPage/SectionHero/SectionHero.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { string } from 'prop-types';
import classNames from 'classnames';

Expand All @@ -8,20 +8,28 @@ import { NamedLink } from '../../../components';
import css from './SectionHero.module.css';

const SectionHero = props => {
const [mounted, setMounted] = useState(false);
const { rootClassName, className } = props;

useEffect(() => {
setMounted(true);
}, []);

const classes = classNames(rootClassName || css.root, className);

return (
<div className={classes}>
<div className={css.heroContent}>
<h1 className={css.heroMainTitle}>
<h1 className={classNames(css.heroMainTitle, { [css.heroMainTitleFEDelay]: mounted })}>
<FormattedMessage id="SectionHero.title" />
</h1>
<h2 className={css.heroSubTitle}>
<h2 className={classNames(css.heroSubTitle, { [css.heroSubTitleFEDelay]: mounted })}>
<FormattedMessage id="SectionHero.subTitle" />
</h2>
<NamedLink name="SearchPage" className={css.heroButton}>
<NamedLink
name="SearchPage"
className={classNames(css.heroButton, { [css.heroButtonFEDelay]: mounted })}
>
<FormattedMessage id="SectionHero.browseButton" />
</NamedLink>
</div>
Expand Down
Loading