diff --git a/CHANGELOG.md b/CHANGELOG.md index d38938b51..4a15c9b13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,25 @@ https://github.com/sharetribe/flex-template-web/ --- -## Upcoming version 2022-XX-XX +## Upcoming version 2023-XX-XX + +## [v12.0.0] 2023-02-14 + +### Updates from upstream (FTW-daily v10.0.0) + +- [add] This adds support for page asset files that can be created in Console. These asset files are + taken into use for + + - LandingPage + - TermsOfServicePage + - PrivacyPolicyPage + - AboutPage + - and other static pages can also be created through Console (they'll be visible in route: + /p/:asset-name/) + + [#1520](https://github.com/sharetribe/ftw-daily/pull/1520) + + [v12.0.0]: https://github.com/sharetribe/ftw-hourly/compare/v11.1.0.../v12.0.0 ## [v11.1.0] 2023-02-07 diff --git a/package.json b/package.json index 351e405c9..b7eb1d077 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "11.1.0", + "version": "12.0.0", "private": true, "license": "Apache-2.0", "dependencies": { @@ -55,11 +55,16 @@ "react-with-direction": "^1.4.0", "redux": "^4.2.0", "redux-thunk": "^2.4.1", + "rehype-react": "^6.2.1", + "rehype-sanitize": "^4.0.0", + "remark-parse": "^9.0.0", + "remark-rehype": "^8.1.0", "seedrandom": "^3.0.5", "sharetribe-flex-sdk": "^1.17.0", "sharetribe-scripts": "6.0.1", "smoothscroll-polyfill": "^0.4.0", "source-map-support": "^0.5.21", + "unified": "^9.2.2", "url": "^0.11.0" }, "devDependencies": { diff --git a/server/csp.js b/server/csp.js index 334a91280..fa5dfe6c7 100644 --- a/server/csp.js +++ b/server/csp.js @@ -40,7 +40,7 @@ const defaultDirectives = { '*.stripe.com', ], fontSrc: [self, data, 'assets-sharetribecom.sharetribe.com', 'fonts.gstatic.com'], - frameSrc: [self, '*.stripe.com'], + frameSrc: [self, '*.stripe.com', '*.youtube-nocookie.com'], imgSrc: [ self, data, @@ -50,7 +50,8 @@ const defaultDirectives = { 'sharetribe.imgix.net', // Safari 9.1 didn't recognize asterisk rule. // Styleguide placeholder images - 'lorempixel.com', + 'picsum.photos', + '*.picsum.photos', 'via.placeholder.com', 'api.mapbox.com', @@ -65,6 +66,9 @@ const defaultDirectives = { 'www.google-analytics.com', 'stats.g.doubleclick.net', + // Youtube (static image) + '*.ytimg.com', + '*.stripe.com', ], scriptSrc: [ diff --git a/server/dataLoader.js b/server/dataLoader.js index 65595753f..1f2687eb2 100644 --- a/server/dataLoader.js +++ b/server/dataLoader.js @@ -9,13 +9,14 @@ exports.loadData = function(requestUrl, sdk, appInfo) { let translations = {}; const store = configureStore({}, sdk); - const dataLoadingCalls = matchedRoutes.reduce((calls, match) => { - const { route, params } = match; - if (typeof route.loadData === 'function' && !route.auth) { - calls.push(store.dispatch(route.loadData(params, query))); - } - return calls; - }, []); + const dataLoadingCalls = () => + matchedRoutes.reduce((calls, match) => { + const { route, params } = match; + if (typeof route.loadData === 'function' && !route.auth) { + calls.push(store.dispatch(route.loadData(params, query))); + } + return calls; + }, []); // First fetch app-wide assets // Then make loadData calls @@ -23,9 +24,9 @@ exports.loadData = function(requestUrl, sdk, appInfo) { // 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(fetchedAppAssets => { + translations = fetchedAppAssets?.translations?.data || {}; + return Promise.all(dataLoadingCalls()); }) .then(() => { return { preloadedState: store.getState(), translations }; diff --git a/src/Routes.js b/src/Routes.js index ae4f6e1b0..0901994f2 100644 --- a/src/Routes.js +++ b/src/Routes.js @@ -26,8 +26,10 @@ const callLoadData = props => { if (shouldLoadData) { dispatch(loadData(match.params, location.search)) .then(() => { - // eslint-disable-next-line no-console - console.log(`loadData success for ${name} route`); + if (props.logLoadDataCalls) { + // This gives good input for debugging issues on live environments, but with test it's not needed. + console.log(`loadData success for ${name} route`); + } }) .catch(e => { log.error(e, 'load-data-failed', { routeName: name }); @@ -35,7 +37,7 @@ const callLoadData = props => { } }; -const setPageScrollPosition = location => { +const setPageScrollPosition = (location, delayed) => { if (!location.hash) { // No hash, scroll to top window.scroll({ @@ -58,12 +60,23 @@ const setPageScrollPosition = location => { block: 'start', behavior: 'smooth', }); + } else { + // A naive attempt to make a delayed call to scrollIntoView + // Note: 300 milliseconds might not be enough, but adding too much delay + // might affect user initiated scrolling. + delayed = window.setTimeout(() => { + const reTry = document.querySelector(location.hash); + reTry.scrollIntoView({ + block: 'start', + behavior: 'smooth', + }); + }, 300); } } }; -const handleLocationChanged = (dispatch, location) => { - setPageScrollPosition(location); +const handleLocationChanged = (dispatch, location, delayed) => { + setPageScrollPosition(location, delayed); const path = canonicalRoutePath(routeConfiguration(), location); dispatch(locationChanged(location, path)); }; @@ -78,9 +91,10 @@ const handleLocationChanged = (dispatch, location) => { */ class RouteComponentRenderer extends Component { componentDidMount() { + this.delayed = null; // Calling loadData on initial rendering (on client side). callLoadData(this.props); - handleLocationChanged(this.props.dispatch, this.props.location); + handleLocationChanged(this.props.dispatch, this.props.location, this.delayed); } componentDidUpdate(prevProps) { @@ -91,7 +105,13 @@ class RouteComponentRenderer extends Component { // This makes it possible to use loadData as default client side data loading technique. // However it is better to fetch data before location change to avoid "Loading data" state. callLoadData(this.props); - handleLocationChanged(this.props.dispatch, this.props.location); + handleLocationChanged(this.props.dispatch, this.props.location, this.delayed); + } + } + + componentWillUnmount() { + if (this.delayed) { + window.clearTimeout(this.resetTimeoutId); } } diff --git a/src/app.js b/src/app.js index 9067811d7..ca94bbdab 100644 --- a/src/app.js +++ b/src/app.js @@ -92,6 +92,8 @@ const setupLocale = () => { export const ClientApp = props => { const { store, hostedTranslations = {} } = props; setupLocale(); + // This gives good input for debugging issues on live environments, but with test it's not needed. + const logLoadDataCalls = config?.env !== 'test'; return ( { - + diff --git a/src/app.test.js b/src/app.test.js index 4229f3fea..0edac8582 100644 --- a/src/app.test.js +++ b/src/app.test.js @@ -14,9 +14,22 @@ afterAll(() => { }); describe('Application - JSDOM environment', () => { - it('renders in the client without crashing', () => { + it('renders the LandingPage without crashing', () => { window.google = { maps: {} }; - const store = configureStore(); + + // LandingPage gets rendered and it calls hostedAsset > fetchPageAssets > sdk.assetByVersion + const pageData = { + data: { + sections: [], + _schema: './schema.json', + }, + meta: { + version: 'bCsMYVYVawc8SMPzZWJpiw', + }, + }; + const resolvePageAssetCall = () => Promise.resolve(pageData); + const fakeSdk = { assetByVersion: resolvePageAssetCall, assetByAlias: resolvePageAssetCall }; + const store = configureStore({}, fakeSdk); const div = document.createElement('div'); ReactDOM.render(, div); delete window.google; diff --git a/src/components/AspectRatioWrapper/AspectRatioWrapper.js b/src/components/AspectRatioWrapper/AspectRatioWrapper.js new file mode 100644 index 000000000..22bc15b7c --- /dev/null +++ b/src/components/AspectRatioWrapper/AspectRatioWrapper.js @@ -0,0 +1,37 @@ +import React from 'react'; +import { node, number, string } from 'prop-types'; +import classNames from 'classnames'; + +import css from './AspectRatioWrapper.module.css'; + +const AspectRatioWrapper = props => { + const { children, className, rootClassName, width, height, ...rest } = props; + const classes = classNames(rootClassName || css.root, className); + + const aspectRatio = (height / width) * 100; + const paddingBottom = `${aspectRatio}%`; + + return ( +
+
+
{children}
+
+
+ ); +}; + +AspectRatioWrapper.defaultProps = { + className: null, + rootClassName: null, + children: null, +}; + +AspectRatioWrapper.propTypes = { + className: string, + rootClassName: string, + width: number.isRequired, + height: number.isRequired, + children: node, +}; + +export default AspectRatioWrapper; diff --git a/src/components/AspectRatioWrapper/AspectRatioWrapper.module.css b/src/components/AspectRatioWrapper/AspectRatioWrapper.module.css new file mode 100644 index 000000000..f9795c681 --- /dev/null +++ b/src/components/AspectRatioWrapper/AspectRatioWrapper.module.css @@ -0,0 +1,21 @@ +.root { + /* Layout */ + display: block; + width: 100%; + position: relative; +} + +/* Firefox doesn't support image aspect ratio inside flexbox */ +/* Aspect ratio for is given inline */ +.aspectPadding { +} + +.aspectBox { + /* Layout - image will take space defined by aspect ratio wrapper */ + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; +} diff --git a/src/components/Avatar/Avatar.example.js b/src/components/Avatar/Avatar.example.js index e1dd9b393..ed07caaaf 100644 --- a/src/components/Avatar/Avatar.example.js +++ b/src/components/Avatar/Avatar.example.js @@ -46,13 +46,13 @@ const userWithProfileImage = { name: 'square-small', width: 240, height: 240, - url: 'https://lorempixel.com/240/240/people/', + url: 'https://picsum.photos/240/240/', }, 'square-small2x': { name: 'square-small2x', width: 480, height: 480, - url: 'https://lorempixel.com/480/480/people/', + url: 'https://picsum.photos/480/480/', }, }, }, diff --git a/src/components/Footer/Footer.js b/src/components/Footer/Footer.js index 770be5423..b27e11c6e 100644 --- a/src/components/Footer/Footer.js +++ b/src/components/Footer/Footer.js @@ -89,7 +89,7 @@ const Footer = props => {
  • - +
  • @@ -104,7 +104,12 @@ const Footer = props => {
  • - +
  • diff --git a/src/components/Page/Page.js b/src/components/Page/Page.js index ee498f491..edfafafd6 100644 --- a/src/components/Page/Page.js +++ b/src/components/Page/Page.js @@ -78,11 +78,12 @@ class PageComponent extends Component { scrollingDisabled, referrer, author, - contentType, + openGraphType, description, facebookImages, published, schema, + socialSharing, tags, title, twitterHandle, @@ -95,7 +96,6 @@ class PageComponent extends Component { }); this.scrollingDisabledChanged(scrollingDisabled); - const referrerMeta = referrer ? : null; const canonicalRootURL = config.canonicalRootURL; const shouldReturnPathOnly = referrer && referrer !== 'unsafe-url'; @@ -105,9 +105,17 @@ class PageComponent extends Component { const siteTitle = config.siteTitle; const schemaTitle = intl.formatMessage({ id: 'Page.schemaTitle' }, { siteTitle }); const schemaDescription = intl.formatMessage({ id: 'Page.schemaDescription' }); - const metaTitle = title || schemaTitle; - const metaDescription = description || schemaDescription; - const facebookImgs = facebookImages || [ + const pageTitle = title || schemaTitle; + const pageDescription = description || schemaDescription; + const { + title: socialSharingTitle, + description: socialSharingDescription, + images1200: socialSharingImages1200, + // Note: we use image with open graph's aspect ratio (1.91:1) also with Twitter + images600: socialSharingImages600, + } = socialSharing || {}; + + const openGraphFallbackImages = [ { name: 'facebook', url: `${canonicalRootURL}${facebookImage}`, @@ -115,7 +123,7 @@ class PageComponent extends Component { height: 630, }, ]; - const twitterImgs = twitterImages || [ + const twitterFallbackImages = [ { name: 'twitter', url: `${canonicalRootURL}${twitterImage}`, @@ -123,25 +131,25 @@ class PageComponent extends Component { height: 314, }, ]; + const facebookImgs = socialSharingImages1200 || facebookImages || openGraphFallbackImages; + const twitterImgs = socialSharingImages600 || twitterImages || twitterFallbackImages; const metaToHead = metaTagProps({ author, - contentType, - description: metaDescription, + openGraphType, + socialSharingTitle: socialSharingTitle || pageTitle, + socialSharingDescription: socialSharingDescription || pageDescription, + description: pageDescription, facebookImages: facebookImgs, twitterImages: twitterImgs, published, tags, - title: metaTitle, twitterHandle, updated, url: canonicalUrl, locale: intl.locale, }); - // eslint-disable-next-line react/no-array-index-key - const metaTags = metaToHead.map((metaProps, i) => ); - const facebookPage = config.siteFacebookPage; const twitterPage = twitterPageURL(config.siteTwitterHandle); const instagramPage = config.siteInstagramPage; @@ -154,7 +162,8 @@ class PageComponent extends Component { // Schema attribute can be either single schema object or an array of objects // This makes it possible to include several different items from the same page. // E.g. Product, Place, Video - const schemaFromProps = Array.isArray(schema) ? schema : [schema]; + const hasSchema = schema != null; + const schemaFromProps = hasSchema && Array.isArray(schema) ? schema : hasSchema ? [schema] : []; const schemaArrayJSONString = JSON.stringify([ ...schemaFromProps, { @@ -173,9 +182,6 @@ class PageComponent extends Component { url: canonicalRootURL, description: schemaDescription, name: schemaTitle, - publisher: { - '@id': `${canonicalRootURL}#organization`, - }, }, ]); @@ -199,12 +205,14 @@ class PageComponent extends Component { lang: intl.locale, }} > - {title} - {referrerMeta} + {pageTitle} + {referrer ? : null} - {metaTags} + {metaToHead.map((metaProps, i) => ( + + ))} @@ -231,13 +239,14 @@ PageComponent.defaultProps = { rootClassName: null, children: null, author: null, - contentType: 'website', + openGraphType: 'website', description: null, facebookImages: null, twitterImages: null, published: null, referrer: null, schema: null, + socialSharing: null, tags: null, twitterHandle: null, updated: null, @@ -254,7 +263,7 @@ PageComponent.propTypes = { // SEO related props author: string, - contentType: string, // og:type + openGraphType: string, // og:type description: string, // page description facebookImages: arrayOf( shape({ @@ -272,8 +281,28 @@ PageComponent.propTypes = { ), published: string, // article:published_time schema: oneOfType([object, array]), // http://schema.org + socialSharing: shape({ + title: string, + description: string, + images1200: arrayOf( + // Page asset file can define this + shape({ + width: number.isRequired, + height: number.isRequired, + url: string.isRequired, + }) + ), + images600: arrayOf( + // Page asset file can define this + shape({ + width: number.isRequired, + height: number.isRequired, + url: string.isRequired, + }) + ), + }), tags: string, // article:tag - title: string.isRequired, // page title + title: string, // page title twitterHandle: string, // twitter handle updated: string, // article:modified_time diff --git a/src/components/PrivacyPolicy/PrivacyPolicy.js b/src/components/PrivacyPolicy/PrivacyPolicy.js deleted file mode 100644 index 074948156..000000000 --- a/src/components/PrivacyPolicy/PrivacyPolicy.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -import css from './PrivacyPolicy.module.css'; - -const PrivacyPolicy = props => { - const { rootClassName, className } = props; - const classes = classNames(rootClassName || css.root, className); - - // prettier-ignore - return ( -
    -

    Last updated: November 22, 2019

    - -

    - Thank you for using Yogatime! Every marketplace business needs Terms of Service and - Privacy Policy agreements. To help you launch your marketplace faster, we've compiled - two templates you can use as a baseline for the agreements between your online marketplace - business and its users. You can access these templates at - https://www.sharetribe.com/docs/operator-guides/free-templates/ -

    - -

    1 Lorem ipsum dolor sit amet

    -

    - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut - labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco - laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in - voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat - cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -

    - -

    2 Sed ut perspiciatis unde

    -

    - Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque - laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi - architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit - aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione - voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, - consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et - dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum - exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi - consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil - molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? -

    - -

    3 At vero eos et accusamus

    -

    - At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium - voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati - cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id - est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam - libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod - maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. - Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut - et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a - sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis - doloribus asperiores repellat -

    -
    - ); -}; - -PrivacyPolicy.defaultProps = { - rootClassName: null, - className: null, -}; - -const { string } = PropTypes; - -PrivacyPolicy.propTypes = { - rootClassName: string, - className: string, -}; - -export default PrivacyPolicy; diff --git a/src/components/PrivacyPolicy/PrivacyPolicy.module.css b/src/components/PrivacyPolicy/PrivacyPolicy.module.css deleted file mode 100644 index 16e59ac55..000000000 --- a/src/components/PrivacyPolicy/PrivacyPolicy.module.css +++ /dev/null @@ -1,39 +0,0 @@ -@import '../../styles/customMediaQueries.css'; - -.root { - & p { - font-weight: var(--fontWeightMedium); - font-size: 15px; - line-height: 24px; - letter-spacing: 0; - /* margin-top + n * line-height + margin-bottom => x * 6px */ - margin-top: 12px; - margin-bottom: 12px; - - @media (--viewportMedium) { - font-weight: var(--fontWeightMedium); - /* margin-top + n * line-height + margin-bottom => x * 8px */ - margin-top: 17px; - margin-bottom: 15px; - } - } - & h2 { - /* Adjust heading margins to work with the reduced body font size */ - margin: 29px 0 13px 0; - - @media (--viewportMedium) { - margin: 32px 0 0 0; - } - } -} - -.lastUpdated { - composes: marketplaceBodyFontStyles from global; - margin-top: 0; - margin-bottom: 55px; - - @media (--viewportMedium) { - margin-top: 0; - margin-bottom: 54px; - } -} diff --git a/src/components/ResponsiveImage/ResponsiveImage.js b/src/components/ResponsiveImage/ResponsiveImage.js index e2429b818..a70bd1cab 100644 --- a/src/components/ResponsiveImage/ResponsiveImage.js +++ b/src/components/ResponsiveImage/ResponsiveImage.js @@ -34,7 +34,7 @@ */ import React from 'react'; -import { arrayOf, string } from 'prop-types'; +import { arrayOf, oneOfType, string } from 'prop-types'; import classNames from 'classnames'; import { FormattedMessage } from '../../util/reactIntl'; import { propTypes } from '../../util/types'; @@ -95,7 +95,7 @@ ResponsiveImage.propTypes = { className: string, rootClassName: string, alt: string.isRequired, - image: propTypes.image, + image: oneOfType([propTypes.image, propTypes.imageAsset]), variants: arrayOf(string).isRequired, noImageMessage: string, }; diff --git a/src/components/SectionThumbnailLinks/SectionThumbnailLinks.example.js b/src/components/SectionThumbnailLinks/SectionThumbnailLinks.example.js index 996a8b2ec..6d5e299ee 100644 --- a/src/components/SectionThumbnailLinks/SectionThumbnailLinks.example.js +++ b/src/components/SectionThumbnailLinks/SectionThumbnailLinks.example.js @@ -8,13 +8,13 @@ export const TwoNamedLinksWithHeadings = { linksPerRow: 2, links: [ { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?1' } }, text: 'Link 1', }, { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?2' } }, text: 'Link 2', @@ -32,19 +32,19 @@ export const ThreeExternalLinksWithHeadings = { linksPerRow: 3, links: [ { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'ExternalLink', href: 'http://example.com/1' }, text: 'Link 1', }, { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'ExternalLink', href: 'http://example.com/2' }, text: 'Link 2', }, { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'ExternalLink', href: 'http://example.com/3' }, text: 'Link 3', @@ -62,25 +62,25 @@ export const FourLinks = { linksPerRow: 2, links: [ { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?1' } }, text: 'Link 1 with quite a long text that tests how the items below align', }, { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?2' } }, text: 'Link 2', }, { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?3' } }, text: 'Link 3', }, { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?4' } }, text: 'Link 4', @@ -96,38 +96,38 @@ export const SixLinks = { linksPerRow: 3, links: [ { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?1' } }, text: 'Link 1', }, { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?2' } }, searchQuery: '?2', text: 'Link 2', }, { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?3' } }, text: 'Link 3', }, { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?4' } }, text: 'Link 4', }, { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?5' } }, text: 'Link 5', }, { - imageUrl: 'https://lorempixel.com/648/448/', + imageUrl: 'https://picsum.photos/648/448/', imageAltText, linkProps: { type: 'NamedLink', name: 'SearchPage', to: { search: '?6' } }, text: 'Link 6', diff --git a/src/components/TermsOfService/TermsOfService.js b/src/components/TermsOfService/TermsOfService.js deleted file mode 100644 index 095203d1b..000000000 --- a/src/components/TermsOfService/TermsOfService.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -import css from './TermsOfService.module.css'; - -const TermsOfService = props => { - const { rootClassName, className } = props; - const classes = classNames(rootClassName || css.root, className); - - // prettier-ignore - return ( -
    -

    Last updated: November 22, 2019

    - -

    - Thank you for using Yogatime! Every marketplace business needs Terms of Service and - Privacy Policy agreements. To help you launch your marketplace faster, we've compiled - two templates you can use as a baseline for the agreements between your online marketplace - business and its users. You can access these templates at - https://www.sharetribe.com/docs/operator-guides/free-templates/ -

    - -

    1 Lorem ipsum dolor sit amet

    -

    - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut - labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco - laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in - voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat - cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -

    - -

    2 Sed ut perspiciatis unde

    -

    - Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque - laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi - architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit - aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione - voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, - consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et - dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum - exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi - consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil - molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? -

    - -

    3 At vero eos et accusamus

    -

    - At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium - voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati - cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id - est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam - libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod - maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. - Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut - et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a - sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis - doloribus asperiores repellat -

    -
    - ); -}; - -TermsOfService.defaultProps = { - rootClassName: null, - className: null, -}; - -const { string } = PropTypes; - -TermsOfService.propTypes = { - rootClassName: string, - className: string, -}; - -export default TermsOfService; diff --git a/src/components/TermsOfService/TermsOfService.module.css b/src/components/TermsOfService/TermsOfService.module.css deleted file mode 100644 index 16e59ac55..000000000 --- a/src/components/TermsOfService/TermsOfService.module.css +++ /dev/null @@ -1,39 +0,0 @@ -@import '../../styles/customMediaQueries.css'; - -.root { - & p { - font-weight: var(--fontWeightMedium); - font-size: 15px; - line-height: 24px; - letter-spacing: 0; - /* margin-top + n * line-height + margin-bottom => x * 6px */ - margin-top: 12px; - margin-bottom: 12px; - - @media (--viewportMedium) { - font-weight: var(--fontWeightMedium); - /* margin-top + n * line-height + margin-bottom => x * 8px */ - margin-top: 17px; - margin-bottom: 15px; - } - } - & h2 { - /* Adjust heading margins to work with the reduced body font size */ - margin: 29px 0 13px 0; - - @media (--viewportMedium) { - margin: 32px 0 0 0; - } - } -} - -.lastUpdated { - composes: marketplaceBodyFontStyles from global; - margin-top: 0; - margin-bottom: 55px; - - @media (--viewportMedium) { - margin-top: 0; - margin-bottom: 54px; - } -} diff --git a/src/components/UserCard/UserCard.example.js b/src/components/UserCard/UserCard.example.js index d52cceb7b..e2f7cc482 100644 --- a/src/components/UserCard/UserCard.example.js +++ b/src/components/UserCard/UserCard.example.js @@ -56,13 +56,13 @@ export const WithProfileImageAndBioCurrentUser = { name: 'square-small', width: 240, height: 240, - url: 'https://lorempixel.com/240/240/people/', + url: 'https://picsum.photos/240/240/', }, 'square-small2x': { name: 'square-small2x', width: 480, height: 480, - url: 'https://lorempixel.com/480/480/people/', + url: 'https://picsum.photos480/480/', }, }, }, @@ -99,13 +99,13 @@ export const WithProfileImageAndBio = { name: 'square-small', width: 240, height: 240, - url: 'https://lorempixel.com/240/240/people/', + url: 'https://picsum.photos/240/240/', }, 'square-small2x': { name: 'square-small2x', width: 480, height: 480, - url: 'https://lorempixel.com/480/480/people/', + url: 'https://picsum.photos/480/480/', }, }, }, diff --git a/src/components/index.js b/src/components/index.js index 42f0976ac..2e0e21e8c 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -34,6 +34,7 @@ export { default as IconSpinner } from './IconSpinner/IconSpinner'; export { default as IconSuccess } from './IconSuccess/IconSuccess'; // Other independent components +export { default as AspectRatioWrapper } from './AspectRatioWrapper/AspectRatioWrapper'; export { default as ExternalLink } from './ExternalLink/ExternalLink'; export { default as ExpandingTextarea } from './ExpandingTextarea/ExpandingTextarea'; export { default as Form } from './Form/Form'; @@ -160,8 +161,6 @@ export { default as LayoutWrapperAccountSettingsSideNav } from './LayoutWrapperA export {default as LoadableComponentErrorBoundary } from './LoadableComponentErrorBoundary/LoadableComponentErrorBoundary' export { default as ModalMissingInformation } from './ModalMissingInformation/ModalMissingInformation'; export { default as ReviewModal } from './ReviewModal/ReviewModal'; -export { default as PrivacyPolicy } from './PrivacyPolicy/PrivacyPolicy'; -export { default as TermsOfService } from './TermsOfService/TermsOfService'; export { default as EditListingAvailabilityPanel } from './EditListingAvailabilityPanel/EditListingAvailabilityPanel'; export { default as EditListingDescriptionPanel } from './EditListingDescriptionPanel/EditListingDescriptionPanel'; export { default as EditListingFeaturesPanel } from './EditListingFeaturesPanel/EditListingFeaturesPanel'; diff --git a/src/containers/AboutPage/AboutPage.js b/src/containers/AboutPage/AboutPage.js deleted file mode 100644 index fe9fe4521..000000000 --- a/src/containers/AboutPage/AboutPage.js +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import config from '../../config'; -import { twitterPageURL } from '../../util/urlHelpers'; -import { StaticPage, TopbarContainer } from '../../containers'; -import { - LayoutSingleColumn, - LayoutWrapperTopbar, - LayoutWrapperMain, - LayoutWrapperFooter, - Footer, - ExternalLink, -} from '../../components'; - -import css from './AboutPage.module.css'; -import image from './about-us-1056.jpg'; - -const AboutPage = () => { - const { siteTwitterHandle, siteFacebookPage } = config; - const siteTwitterPage = twitterPageURL(siteTwitterHandle); - - // prettier-ignore - return ( - - - - - - - -

    Find new depths in your yoga practice

    - My first ice cream. - -
    -
    -

    Yoga was listed by UNESCO as an intangible cultural heritage.

    -
    - -
    -

    - Each yoga practitioner is an individual, and each one of us needs different care. - Working together with an experienced yoga teacher offers the possibility to rise - our practise to a whole new level. -

    - -

    - Whether it is the alignment of asanas or being able to set the focus of the class, - we all have our own struggles and goals. Some of these cannot be addressed in a - regular class of twenty yogis. Working together with the experienced yoga teachers - from Yogatime, you can together create just the right class for you. -

    - -

    Are you a yoga teacher?

    - -

    - Yogatime offers you a platform through which you can reach thousands of yoga - practitioners. Offering private yoga classes through Yogatime offers you a - possibility to grow your customer base and earn some extra income on top of your - regular classes. -

    - -

    - Create your own marketplace like Yogatime -

    -

    - Yogatime is brought to you by{' '} - Sharetribe. Sharetribe - offers anyone a possibility to create a marketplace without restricting your own - creativity. Do not hesitate to reach out and learn how to best turn your - marketplace idea to reality. -

    -

    - You can also checkout our{' '} - Facebook and{' '} - Twitter. -

    -
    -
    -
    - - -