Merge remote-tracking branch 'origin/develop' into release/2022-03-02

This commit is contained in:
kchadha 2022-03-02 16:10:32 +00:00
commit 816e0f045e
32 changed files with 36463 additions and 1786 deletions

46
bin/announce.js Normal file
View file

@ -0,0 +1,46 @@
// this will announce that a deploy successfully finished into slack
const https = require('https');
let environment = process.env.SCRATCH_ENV || 'unknown environment';
let branch = process.env.CIRCLE_BRANCH || 'unknown branch';
let urlEng = process.env.SLACK_WEBHOOK_ENGINEERING;
let urlMod = process.env.SLACK_WEBHOOK_MODS;
let urlNotifications = process.env.SLACK_WEBHOOK_CIRCLECI_NOTIFICATIONS;
let announcement = {text: `scratch-www has deployed branch ${branch} to ${environment}.`};
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
const postMessage = (url, message, channelName) => {
let data = JSON.stringify(message);
const req = https.request(url, options, res => {
console.log(`statusCode: ${res.statusCode}`); // eslint-disable-line no-console
if (res.statusCode === 200) {
process.stdout.write(`announced to ${channelName}\n` + JSON.stringify(message) + '\n');
} else {
process.stdout.write(`FAILED to announce to slack`);
}
});
req.on('error', error => {
console.error(error); // eslint-disable-line no-console
});
req.write(data);
req.end();
};
postMessage(urlNotifications, announcement, '#circleci-notifications');
if (environment === 'production'){
postMessage(urlEng, announcement, '#engineering');
postMessage(urlMod, announcement, '#scratch-mods');
}

36932
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,7 @@
"test:coverage": "tap ./test/{unit-legacy,localization-legacy}/ --coverage --coverage-report=lcov",
"build": "npm run clean && npm run translate && NODE_OPTIONS=--max_old_space_size=8000 webpack --bail",
"clean": "rm -rf ./build && rm -rf ./intl && mkdir -p build && mkdir -p intl",
"deploy": "npm run deploy:s3 && npm run deploy:fastly",
"deploy": "npm run deploy:s3 && npm run deploy:fastly && npm run deploy:announce",
"deploy:fastly": "node ./bin/configure-fastly.js",
"deploy:s3": "npm run deploy:s3:all && npm run deploy:s3:svg && npm run deploy:s3:js && npm run deploy:s3:css",
"deploy:s3cmd": "s3cmd sync -P --delete-removed --add-header=Cache-Control:no-cache,public,max-age=3600 --add-header=x-amz-meta-surrogate-key:static-assets",
@ -26,6 +26,7 @@
"deploy:s3:svg": "npm run deploy:s3cmd -- --exclude '*' --include '*.svg' --mime-type 'image/svg+xml' ./build/ s3://$S3_BUCKET_NAME/",
"deploy:s3:js": "npm run deploy:s3cmd -- --exclude '*' --include '*.js' --mime-type 'application/javascript' ./build/ s3://$S3_BUCKET_NAME/",
"deploy:s3:css": "npm run deploy:s3cmd -- --exclude '*' --include '*.css' --mime-type 'text/css' ./build/ s3://$S3_BUCKET_NAME/",
"deploy:announce": "node ./bin/announce.js",
"i18n:push": "./bin/tx-push-www --execute",
"translate:urls": "node ./bin/get-localized-urls localized-urls.json",
"translate:files": "node ./bin/build-locales node_modules/scratch-l10n/www intl",
@ -49,9 +50,11 @@
"express-http-proxy": "1.1.0",
"lodash.defaults": "4.0.1",
"lodash.get": "^4.4.2",
"react-confetti": "^6.0.1",
"react-helmet": "5.2.0",
"react-router-dom": "5.2.0",
"react-twitter-embed": "^3.0.3",
"react-use": "^17.3.1",
"scratch-parser": "5.0.0",
"scratch-storage": "0.5.1"
},
@ -72,6 +75,7 @@
"babel-preset-es2015": "6.22.0",
"babel-preset-react": "6.22.0",
"bowser": "1.9.4",
"canvas": "^2.9.0",
"chromedriver": "96.0.0",
"classnames": "2.2.5",
"cookie": "0.4.1",
@ -95,6 +99,7 @@
"html-webpack-plugin": "3.2.0",
"iso-3166-2": "0.4.0",
"jest": "23.6.0",
"jest-canvas-mock": "^2.3.1",
"jest-junit": "12.0.0",
"keymirror": "0.1.1",
"lodash.bindall": "4.4.0",
@ -105,6 +110,7 @@
"lodash.uniqby": "4.7.0",
"mini-css-extract-plugin": "^1.6.2",
"minilog": "2.0.8",
"sass": "1.49.7",
"pako": "0.2.8",
"plotly.js": "1.47.4",
"postcss": "8.4.6",
@ -126,10 +132,9 @@
"redux-mock-store": "1.5.4",
"redux-thunk": "2.0.1",
"regenerator-runtime": "0.13.9",
"sass": "1.49.7",
"sass-loader": "10.2.1",
"scratch-gui": "0.1.0-prerelease.20220219014254",
"scratch-l10n": "3.14.20220217031602",
"scratch-gui": "0.1.0-prerelease.20220302125034",
"scratch-l10n": "3.14.20220302031532",
"selenium-webdriver": "4.1.0",
"slick-carousel": "1.6.0",
"style-loader": "0.12.3",
@ -143,7 +148,8 @@
},
"jest": {
"setupFiles": [
"<rootDir>/test/helpers/enzyme-setup.js"
"<rootDir>/test/helpers/enzyme-setup.js",
"jest-canvas-mock"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/fileMock.js",

View file

@ -13,6 +13,7 @@ const EmailConfirmationModal = ({
return (
<Modal
className="email-confirmation-modal"
isOpen={isOpen}
showCloseButton
useStandardSizes

View file

@ -2,7 +2,7 @@
@import "../../../frameless";
.modal-content{
.email-confirmation-modal{
border-radius: 4px;
overflow: hidden;
max-width: 500px;

View file

@ -7,9 +7,9 @@ require('./not-available.scss');
const ProjectNotAvailable = () => (
<React.Fragment>
<Helmet>
<title>Scratch - Imagine, Program, Share</title>
</Helmet>
{/* Adding title as a prop rather than a child prevents a
recursion error when used in a component with useEffect */}
<Helmet title={'Scratch - Imagine, Program, Share'} />
<div className="not-available-outer">
<FlexRow className="inner">
<img

View file

@ -269,6 +269,13 @@
"view": "scratch_1.4/scratch_1.4",
"title": "Scratch 1.4"
},
{
"name": "become-a-scratcher",
"pattern": "^/become-a-scratcher/?$",
"routeAlias": "/become-a-scratcher",
"view": "become-a-scratcher/become-a-scratcher",
"title": "Become a Scratcher"
},
{
"name": "download-scratch2",
"pattern": "^/download/scratch2/?(\\?.*)?$",

View file

@ -0,0 +1,584 @@
/* eslint-disable react/jsx-no-bind */
import React, {useState, useEffect} from 'react';
import useWindowSize from 'react-use/lib/useWindowSize';
import Confetti from 'react-confetti';
import {FormattedMessage, injectIntl} from 'react-intl';
import PropTypes from 'prop-types';
import render from '../../lib/render.jsx';
import thumbnailUrl from '../../lib/user-thumbnail';
import {connect} from 'react-redux';
import sessionActions from '../../redux/session.js';
import api from '../../lib/api';
import Button from '../../components/forms/button.jsx';
import Modal from '../../components/modal/base/modal.jsx';
import NotAvailable from '../../components/not-available/not-available.jsx';
import WarningBanner from '../../components/title-banner/warning-banner.jsx';
require('./become-a-scratcher.scss');
const communityGuidelines = [
{
section: 'becomeAScratcher.guidelines.respectSection',
header: 'becomeAScratcher.guidelines.respectHeader',
body: 'becomeAScratcher.guidelines.respectBody',
image: 'respect-illustration.svg',
imageLeft: true
},
{
section: 'becomeAScratcher.guidelines.safeSection',
header: 'becomeAScratcher.guidelines.safeHeader',
body: 'becomeAScratcher.guidelines.safeBody',
image: 'safe-illustration.svg'
},
{
section: 'becomeAScratcher.guidelines.feedbackSection',
header: 'becomeAScratcher.guidelines.feedbackHeader',
body: 'becomeAScratcher.guidelines.feedbackBody',
image: 'feedback-illustration.svg',
imageLeft: true
},
{
section: 'becomeAScratcher.guidelines.remix1Section',
header: 'becomeAScratcher.guidelines.remix1Header',
body: 'becomeAScratcher.guidelines.remix1Body',
image: 'remix-illustration-1.svg'
},
{
section: 'becomeAScratcher.guidelines.remix2Section',
header: 'becomeAScratcher.guidelines.remix2Header',
body: 'becomeAScratcher.guidelines.remix2Body',
image: 'remix-illustration-2.svg'
},
{
section: 'becomeAScratcher.guidelines.remix3Section',
header: 'becomeAScratcher.guidelines.remix3Header',
body: 'becomeAScratcher.guidelines.remix3Body',
image: 'remix-illustration-3.svg'
},
{
section: 'becomeAScratcher.guidelines.honestSection',
header: 'becomeAScratcher.guidelines.honestHeader',
body: 'becomeAScratcher.guidelines.honestBody',
image: 'honest-illustration.svg',
imageLeft: true
},
{
section: 'becomeAScratcher.guidelines.friendlySection',
header: 'becomeAScratcher.guidelines.friendlyHeader',
body: 'becomeAScratcher.guidelines.friendlyBody',
image: 'friendly-illustration.svg'
}
];
/* eslint-disable max-len */
const confettiPaths = [
new Path2D('M34.549 3.1361L40.613 15.4191L54.1718 17.3947C58.7918 18.0636 60.6345 23.7378 57.2946 26.9979L47.4788 36.5612L49.7954 50.0668C50.5839 54.6646 45.7557 58.1728 41.6274 56.0024L29.4994 49.6239L17.3714 56.0024C13.2386 58.1728 8.4149 54.6646 9.19893 50.0668L11.52 36.5612L1.70862 26.9979C-1.63567 23.7378 0.20701 18.0636 4.82699 17.3947L18.3902 15.4191L24.4497 3.1361C26.5183 -1.04537 32.4805 -1.04537 34.549 3.1361Z'),
new Path2D('M58.9044 21.997C57.1803 41.4894 35.1024 56.8198 29.5019 56.8198C23.9015 56.8198 1.81974 41.4894 0.0993974 21.997C0.0496987 21.3611 0 20.7252 0 20.0893C0 10.6382 7.71859 2.9502 17.211 2.9502C22.028 2.9502 26.4053 4.90743 29.5019 8.14029C32.5985 4.90743 36.9758 2.9502 41.7928 2.9502C51.2814 2.9502 59 10.6382 59 20.0893C59 20.7252 58.9503 21.3611 58.9044 21.997Z'),
new Path2D('M10.9967 45.1305C5.99892 36.9346 5.59074 25.9968 9.99448 17.3259C14.5575 8.00641 22.2367 3.87569 25.3229 2.53264C27.178 1.67111 29.1327 0.999586 31.2366 0.504948L33.3008 0.0594462C34.8174 -0.25175 36.3273 0.68839 36.6558 2.18868C36.9877 3.69225 36.022 5.17289 34.4988 5.50047L32.491 5.93286C30.8218 6.32595 29.2223 6.87628 27.6625 7.6035C25.6349 8.4814 18.9281 11.8783 15.0586 19.786C12.1217 25.5612 11.2456 34.7431 15.8683 42.3199C20.0564 49.6183 28.6315 54.0929 36.7654 53.3395C44.4578 52.7859 51.5164 47.6659 53.992 40.8851C56.5042 34.5072 54.835 27.9688 52.0772 24.2869C48.7553 19.75 44.6204 18.3545 42.9976 17.9647C42.7554 17.8926 37.074 16.186 32.0563 18.6985C29.8959 19.7336 27.1017 22.0627 25.605 25.5612C23.9889 29.1743 24.407 33.8193 26.5973 36.9051C28.8008 40.2136 32.9556 42.0873 36.5297 41.4518C40.0872 40.8917 42.4932 38.2449 43.064 35.9191C43.7343 33.4197 42.7056 31.3068 41.9025 30.5468C40.5386 29.2005 39.3605 29.1776 39.3107 29.1743C38.7532 29.1579 38.4777 29.2005 38.3118 29.2267C37.6746 29.4593 36.772 29.9703 36.5065 30.5075C36.46 30.596 36.3406 30.8384 36.5696 31.4444C37.1171 32.8825 36.3771 34.4908 34.9202 35.0313C33.47 35.5751 31.8373 34.8446 31.2864 33.4033C30.3804 31.0186 30.8815 29.1514 31.4623 28.0049C32.9789 25.0207 36.4999 23.9397 36.8981 23.825C37.0806 23.7693 37.2731 23.7366 37.4623 23.7202C37.8505 23.6612 38.5342 23.5695 39.46 23.6055C41.5806 23.6317 43.9965 24.7389 45.8483 26.5701C48.0352 28.6338 49.7476 32.7809 48.533 37.2883C47.3483 42.1135 42.7952 46.1034 37.4822 46.9387C31.7676 47.9575 25.3329 45.1403 21.9347 40.0334C18.6593 35.431 18.032 28.696 20.4281 23.3533C23.0564 17.2014 28.0773 14.4171 29.5508 13.7095C36.6359 10.1717 44.2089 12.5073 44.5275 12.6056C46.5651 13.0871 52.2299 14.9838 56.6336 21.0013C60.0186 25.5284 62.763 33.9831 59.2752 42.8342C56.0894 51.5575 47.0197 58.1843 37.2399 58.8886C36.4733 58.9607 35.6968 59 34.9236 59C25.4325 59 15.8186 53.5295 10.9967 45.1305Z')
];
const OnboardingHeader = ({user, sectionText, secondary}) => {
const [showModal, setShowModal] = useState(false);
return (
<div className="header">
{/* Finish Later Modal */}
<Modal
isOpen={showModal}
showCloseButton
useStandardSizes
className="mod-join"
shouldCloseOnOverlayClick={false}
onRequestClose={() => setShowModal(false)}
>
<div className="finish-later-modal-header">
<FormattedMessage
id={'becomeAScratcher.buttons.finishLater'}
/>
</div>
<div className="finish-later-modal-content">
<h3>
<FormattedMessage
id={'becomeAScratcher.finishLater.header'}
/>
</h3>
<div>
<FormattedMessage
id={'becomeAScratcher.finishLater.body'}
/>
<br />
<FormattedMessage
id={'becomeAScratcher.finishLater.clickBecomeAScratcher'}
/>
</div>
<img
className="profile-page-image"
src="/images/onboarding/profile-page-become-a-scratcher-button.svg"
/>
<a href={`/users/${user.username}`}>
<Button>
<FormattedMessage
id={'becomeAScratcher.buttons.backToProfile'}
/>
</Button>
</a>
</div>
</Modal>
<span>
<span className="section">{sectionText}</span>
</span>
<Button
onClick={() => setShowModal(true)}
className={`finish-later ${secondary ? 'secondary-finish-later' : ''}`}
>
<FormattedMessage
id={'becomeAScratcher.buttons.finishLater'}
/>
</Button>
</div>
);
};
OnboardingHeader.propTypes = {
user: PropTypes.shape({
id: PropTypes.number,
thumbnailUrl: PropTypes.string,
username: PropTypes.string
}),
sectionText: PropTypes.string,
secondary: PropTypes.bool
};
const OnboardingNavigation = ({currentPage, totalDots, onNextPage, onBackPage, nextButtonText}) => {
const dots = [];
if (currentPage && totalDots){
for (let i = 0; i < totalDots; i++){
// First two pages don't have dots
dots.push(<div className={`dot ${currentPage === i + 2 && 'active'}`} />);
}
}
return (
<div className="navigation">
<Button onClick={onBackPage}>
<img
className="left-arrow"
alt=""
src="/images/onboarding/left-arrow.svg"
/>
<span className="navText">
<FormattedMessage
id={'becomeAScratcher.buttons.back'}
/>
</span>
</Button>
{(currentPage && totalDots) &&
<div className="dotRow">
{dots}
</div>}
<Button onClick={onNextPage}>
<span className="navText">
{nextButtonText || <FormattedMessage id={'becomeAScratcher.buttons.next'} />}
</span>
<img
className="right-arrow"
alt=""
src="/images/onboarding/right-arrow.svg"
/>
</Button>
</div>
);
};
OnboardingNavigation.propTypes = {
currentPage: PropTypes.number,
totalDots: PropTypes.number,
onNextPage: PropTypes.func,
onBackPage: PropTypes.func,
nextButtonText: PropTypes.string
};
const BecomeAScratcher = ({user, invitedScratcher, scratcher, sessionStatus}) => {
const [currentPage, setCurrentPage] = useState(0);
const [hoorayAppear, setHoorayAppear] = useState(false);
const [showConfetti, setShowConfetti] = useState(true);
const [showPromotionError, setShowPromotionError] = useState(false);
const {width, height} = useWindowSize();
const lastPage = 3 + communityGuidelines.length;
const handlePromoteToScratcher = onSuccess => {
api({
host: '',
uri: `/users/${user.username}/promote-to-scratcher/`,
method: 'GET'
}, err => {
if (err) {
return setShowPromotionError(true);
}
onSuccess();
});
};
// Preload images
useEffect(() => {
communityGuidelines.forEach(guideline => {
new Image().src = `/images/onboarding/${guideline.image}`;
});
new Image().src = '/images/onboarding/community-guidelines.svg';
new Image().src = '/images/onboarding/create-a-project.svg';
new Image().src = '/images/onboarding/right-arrow.svg';
new Image().src = '/images/onboarding/left-arrow.svg';
}, []);
useEffect(() => {
if (user){
// If user is a scratcher only show last page
if (scratcher){
setCurrentPage(lastPage);
}
}
}, [user]);
useEffect(() => {
if (currentPage === lastPage){
setTimeout(() => {
setHoorayAppear(true);
}, 2500);
setTimeout(() => {
setShowConfetti(false);
}, 5000);
}
}, [currentPage]);
const nextPage = () => {
window.scrollTo(0, 0);
setCurrentPage(Math.min(currentPage + 1, lastPage));
};
const backPage = () => {
window.scrollTo(0, 0);
setCurrentPage(Math.max(currentPage - 1, 0));
};
if (sessionStatus === sessionActions.Status.FETCHED){
// Not logged in
if (!user){
return (<NotAvailable />);
}
// New scratcher who is not invited
if (!invitedScratcher && !scratcher){
return (<div className="no-invitation">
<img
className="profile-page-image"
src="/images/onboarding/invitation-illustration.svg"
/>
<h2>
<FormattedMessage
id={'becomeAScratcher.noInvitation.header'}
/>
</h2>
<div>
<FormattedMessage
id={'becomeAScratcher.noInvitation.body'}
/>
</div>
</div>);
}
// Invited Scratcher
if (currentPage === 0){
return (
<div className="onboarding col">
<div className="congratulations-page">
<OnboardingHeader user={user} />
<div className="congratulations-image-layout">
<div className="congratulations-image-container">
<img
className="congrats-banner-image"
alt=""
src={`/images/onboarding/congratulations-illustration.svg`}
/>
<img
className="congratulations-avatar"
src={thumbnailUrl(user.id, 100, 100)}
/>
<h3
className="congratulations-username"
>
{user.username}
</h3>
</div>
</div>
<div className="congratulations-text-layout">
<div className="congratulations-text">
<h1>
<FormattedMessage
id={'becomeAScratcher.congratulations.header'}
values={{username: user.username}}
/>
</h1>
<FormattedMessage
id={'becomeAScratcher.congratulations.body'}
/>
<div />
</div>
<Button onClick={nextPage}>
<FormattedMessage
id={'becomeAScratcher.buttons.getStarted'}
/>
<img
className="right-arrow"
alt=""
src="/images/onboarding/right-arrow.svg"
/>
</Button>
</div>
</div>
</div>
);
} else if (currentPage === 1){
return (
<div className="onboarding col">
<OnboardingHeader user={user} />
<div className="content">
<div className="opening-text-content">
<h1><FormattedMessage id={'becomeAScratcher.toBeAScratcher.header'} /></h1>
<div>
<FormattedMessage id={'becomeAScratcher.toBeAScratcher.body'} />
</div>
<br />
<b>
<FormattedMessage id={'becomeAScratcher.toBeAScratcher.definition'} />
</b>
</div>
<div className="opening-text-content">
<FormattedMessage id={'becomeAScratcher.toBeAScratcher.canDo'} />
<div className="labeled-icon">
<img
alt=""
src="/images/onboarding/create-studios.svg"
/>
<FormattedMessage id={'becomeAScratcher.toBeAScratcher.createStudios'} />
</div>
<div className="labeled-icon">
<img
alt=""
src="/images/onboarding/help-out.svg"
/>
<FormattedMessage id={'becomeAScratcher.toBeAScratcher.helpOut'} />
</div>
<FormattedMessage id={'becomeAScratcher.toBeAScratcher.communityGuidelines'} />
</div>
</div>
<OnboardingNavigation
onNextPage={nextPage}
onBackPage={backPage}
nextButtonText={<FormattedMessage id={'becomeAScratcher.buttons.communityGuidelines'} />}
/>
</div>
);
} else if (currentPage < 2 + communityGuidelines.length) {
const guideline = communityGuidelines[currentPage - 2];
return (
<div className="onboarding col">
<OnboardingHeader
user={user}
section={(<FormattedMessage id={guideline.section} />)}
/>
<div className="content">
{guideline.imageLeft && (
<div className="image-content">
<img
alt=""
src={`/images/onboarding/${guideline.image}`}
/>
</div>
)}
<div className="text-content">
<h1><FormattedMessage id={guideline.header} /></h1>
<div>
<FormattedMessage id={guideline.body} />
</div>
</div>
{!guideline.imageLeft && (
<div className="image-content">
<div className="image-inner-content">
<img
alt=""
src={`/images/onboarding/${guideline.image}`}
/>
{currentPage === 3 && <img
className="security-avatar"
src={thumbnailUrl(user.id, 100, 100)}
/>}
</div>
</div>
)}
</div>
<OnboardingNavigation
currentPage={currentPage}
totalDots={communityGuidelines.length}
onNextPage={nextPage}
onBackPage={backPage}
/>
</div>
);
} else if (currentPage === lastPage - 1) {
return (<div className="onboarding blue-background col">
{
showPromotionError &&
<WarningBanner>
<FormattedMessage id={'becomeAScratcher.success.error'} />
</WarningBanner>
}
<OnboardingHeader
secondary
user={user}
/>
<div className="content center-flex">
<div className="invitation-card">
<div className="invitation-image row center-flex">
<img
alt=""
src={`/images/onboarding/left-lines.svg`}
/>
<img
className="invitation-icon"
src={thumbnailUrl(user.id, 100, 100)}
/>
<img
alt=""
src={`/images/onboarding/right-lines.svg`}
/>
</div>
<h1>
<FormattedMessage
id={'becomeAScratcher.invitation.header'}
values={{username: user.username}}
/>
</h1>
<div className="content">
<FormattedMessage
id={'becomeAScratcher.invitation.body'}
/>
</div>
<br />
<div className="content">
<FormattedMessage
id={'becomeAScratcher.invitation.finishLater'}
/>
</div>
<div className="invitation-buttons">
<Button
className="go-back"
onClick={backPage}
>
<FormattedMessage id={'becomeAScratcher.buttons.goBack'} />
</Button>
<Button
onClick={() => {
handlePromoteToScratcher(nextPage);
}}
>
<FormattedMessage id={'becomeAScratcher.buttons.iAgree'} />
</Button>
</div>
</div>
</div>
</div>);
} else if (currentPage === lastPage) {
return (<div className="onboarding col">
<div className="hooray-screen">
<div className={`hooray-confetti ${hoorayAppear && 'hooray-disappear'}`}>
{showConfetti && confettiPaths.map(confettiPath =>
(<Confetti
key={confettiPath.toString()}
colors={['#0FBD8C', '#4C97FF', '#FFBF00', '#FF6680']}
gravity={.08}
width={width}
height={height}
friction={.9999}
numberOfPieces={45}
initialVelocityY={-10}
initialVelocityX={1}
drawShape={ctx => {
ctx.scale(.5, .5);
ctx.translate(-30, -30);
ctx.fill(confettiPath);
}}
/>)
)}
</div>
<h1><FormattedMessage id={'becomeAScratcher.success.header'} /></h1>
<div className={`hooray-links ${hoorayAppear && 'hooray-appear'}`}>
<div>
<FormattedMessage id={'becomeAScratcher.success.body'} />
</div>
<div className="row">
<a
className="hooray-link"
href={hoorayAppear ? '/community_guidelines' : null}
>
<img
className="profile-page-image"
src="/images/onboarding/community-guidelines.svg"
/>
<FormattedMessage id={'becomeAScratcher.success.communityGuidelines'} />
</a>
<a
className="hooray-link"
href={hoorayAppear ? '/projects/editor' : null}
>
<img
className="profile-page-image"
src="/images/onboarding/create-a-project.svg"
/>
<FormattedMessage id={'becomeAScratcher.success.createAProject'} />
</a>
</div>
<a href={hoorayAppear ? '/' : null}>
<Button>
<FormattedMessage id={'becomeAScratcher.buttons.takeMeBack'} />
</Button>
</a>
</div>
</div>
</div>);
}
}
return (null);
};
BecomeAScratcher.propTypes = {
user: PropTypes.shape({
id: PropTypes.number,
thumbnailUrl: PropTypes.string,
username: PropTypes.string
}),
invitedScratcher: PropTypes.bool,
scratcher: PropTypes.bool,
sessionStatus: PropTypes.string
};
const mapStateToProps = state => ({
user: state.session && state.session.session && state.session.session.user,
invitedScratcher: state.session && state.session.session && state.session.session.permissions && state.session.session.permissions.invited_scratcher,
scratcher: state.session && state.session.session && state.session.session.permissions && state.session.session.permissions.scratcher,
sessionStatus: state.session.status
});
export const ConnectedBecomeAScratcher = connect(
mapStateToProps
)(BecomeAScratcher);
const IntlConnectedScratchedOnboarding = injectIntl(ConnectedBecomeAScratcher);
render(<IntlConnectedScratchedOnboarding />, document.getElementById('app'));

View file

@ -0,0 +1,473 @@
@import "../../colors";
@import "../../frameless";
html,body{
background-color: $ui-white !important;
}
.col{
display: flex;
flex-direction: column !important;
}
.row{
display: flex;
flex-direction: row;
}
.center-flex{
display:flex;
justify-content: center;
align-items: center;
}
.button{
display: flex;
justify-content: center;
align-items: center;
border-radius: .3rem;
.left-arrow{
padding-right: 4px;
@media #{$small} {
padding: 0px;
}
}
.right-arrow{
padding-left: 4px;
@media #{$small} {
padding: 0px;
}
}
}
.no-invitation{
display: flex;
max-width: 640px;
margin: auto;
height: 100vh;
flex-direction: column;
text-align: center;
justify-content: center;
align-items: center;
line-height: 1.8rem;
padding: 20px;
box-sizing: border-box;
h2{
margin: 20px 0px;
}
}
.header{
display: flex;
justify-content: space-between;
align-items: center;
margin: 10px 40px;
.section{
@media #{$medium-and-smaller} {
display: none;
}
}
}
.modal-overlay{
background-color: rgba(0,0,0, 0.5) !important;
}
.modal-content{
line-height: 1.8rem !important;
box-shadow: none !important;
border-radius: 8px !important;
.finish-later-modal-header{
background-color: $ui-blue;
color: $ui-white;
height: 46px;
display:flex;
justify-content: center;
align-items: center;
border-radius: 7px 7px 0px 0px;
}
.finish-later-modal-content{
padding: 20px 30px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
button{
padding: 5px 10px;
}
}
h3{
margin-bottom: 10px;
text-align: center;
}
.profile-page-image{
margin: 35px;
width: 100%;
max-width: 400px;
}
}
.congratulations-page{
display: flex;
align-items: normal;
min-height: 100vh;
@media #{$medium-and-smaller} {
flex-direction: column;
}
.header{
position: absolute;
top: 0px;
right: 0px;
left: 0px;
@media #{$medium-and-smaller} {
position: static;
flex-direction: row;
}
}
.congratulations-text-layout{
flex: 1;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 40px;
justify-content: space-between;
@media #{$medium-and-smaller} {
padding-top: 0px;
}
.center-text{
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
}
.congratulations-text{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
max-width: 700px;
margin: auto;
}
}
.congratulations-image-layout{
flex: .9;
display: flex;
position:relative;
justify-content: center;
background-color: #E9F1FC;
@media #{$medium-and-smaller} {
width: 100vw;
order: -1;
margin-top: -90px;
}
.congratulations-image-container{
position: relative;
display: flex;
margin: auto;
height: 375px;
}
}
.congrats-banner-image{
width: 328px;
}
}
.invitation-card{
background-color: white;
margin: 10px;
border-radius: 7px;
padding: 40px;
text-align: center;
max-width: 600px;
.invitation-image{
margin-bottom: 20px;
}
.invitation-icon{
border-radius: 3px;
width: 50px;
margin: 0px 10px;
}
button{
margin: 10px;
margin-top: 30px;
}
.invitation-buttons{
display: flex;
justify-content: center;
}
}
.blue-background{
background-color: #3373cc;
}
.labeled-icon{
display: flex;
justify-content: center;
align-items: center;
img{
margin-right: 20px;
}
}
.navigation{
display: flex;
width: 100%;
justify-content: space-around;
align-items: center;
margin-bottom: 40px;
.dotRow{
display: flex;
justify-content: space-between;
width: 244px;
min-width: 0px;
padding: 0px 5px;
}
.dot{
width: 20px;
height: 20px;
border-radius: 100%;
background-color: #d9d9d9;
border: none;
display: flex;
justify-content: center;
align-items: center;
}
.active{
border: solid 2px #4280d9;
background-color: #4c97fe;
margin-top: -2px;
}
.navText{
@media #{$small} {
display: none;
}
}
}
.onboarding {
display: flex;
flex-direction: row;
min-height: 100vh;
line-height: 1.8rem;
font-size: 1.1rem;
h1{
line-height: 3rem;
font-size: 2rem;
margin-bottom: 20px;
}
button{
padding: .3em .9em;
}
.content{
flex: 1;
display: flex;
max-width: 1000px;
padding: auto;
margin: auto;
@media #{$medium-and-smaller} {
flex-direction: column;
}
}
button.finish-later{
background-color: rgba(0,0,0,0);
color: $ui-blue;
border: solid 1px $ui-blue;
}
button.secondary-finish-later{
background-color: rgba(0,0,0,0);
color: $ui-white;
border: solid 1px $ui-white;
}
button.go-back{
background-color: $ui-white;
color: $ui-blue;
border: solid 1px $ui-blue;
}
.image-content{
display: flex;
position: relative;
flex: 4;
justify-content: center;
align-items: center;
@media #{$medium-and-smaller} {
flex: 12;
order: 1;
align-items: flex-end;
}
img{
@media #{$medium-and-smaller} {
width: 300px;
height: 300px;
}
}
}
.image-inner-content{
position: relative;
}
.image-content-1{
display: flex;
flex: 4;
justify-content: center;
align-items: center;
@media #{$medium-and-smaller} {
flex: 12;
order: 1;
align-items: flex-end;
height: 250px;
}
img{
z-index: 100;
@media #{$medium-and-smaller} {
width: 300px;
height: 300px;
}
}
@media #{$medium-and-smaller} {
background-color: #E9F1FC;
}
}
.text-content{
flex: 8;
display: flex;
justify-content: center;
align-items: flex-start;
flex-direction: column;
padding: 40px;
@media #{$medium-and-smaller} {
flex: 12;
order: 2;
padding-top: 0px;
justify-content: flex-start;
}
}
.opening-text-content{
flex: 6;
display: flex;
justify-content: center;
// align-items: center;
flex-direction: column;
padding: 40px;
display: flex;
align-items: flex-start;
@media #{$medium-and-smaller} {
flex: 12;
order: 2;
padding-top: 0px;
}
}
}
.hooray-screen{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: auto;
h1{
text-align: center;
}
.hooray-links{
opacity: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.hooray-link{
display: flex;
margin: 15px;
margin-top: 40px;
margin-bottom: 40px;
flex-direction: column;
justify-content: center;
align-items: center;
}
.hooray-confetti{
opacity: 1;
}
.hooray-appear{
opacity: 1;
transition: opacity 1s ease-in-out;
}
.hooray-disappear{
opacity: 0;
transition: opacity 1s ease-in-out;
}
}
.congratulations-avatar{
position: absolute;
border-radius: 15%;
width: 60px;
height: 60px;
top: 170px;
left: 124px;
}
.congratulations-username{
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
position: absolute;
top: 231px;
left: -18px;
right: 0;
}
.security-avatar{
position: absolute;
border-radius: 100%;
width: 65px;
height: 65px;
top: 172px;
left: 149px;
@media #{$medium-and-smaller} {
width: 60px !important;
height: 60px !important;
top: 140.5px;
left: 121.5px;
}
}

View file

@ -0,0 +1,59 @@
{
"becomeAScratcher.buttons.back": "Back",
"becomeAScratcher.buttons.next": "Next",
"becomeAScratcher.buttons.communityGuidelines": "Community Guidelines",
"becomeAScratcher.buttons.getStarted": "Get Started",
"becomeAScratcher.buttons.finishLater": "Finish Later",
"becomeAScratcher.buttons.goBack": "Go Back",
"becomeAScratcher.buttons.iAgree": "I Agree",
"becomeAScratcher.buttons.takeMeBack": "Take me back to Scratch",
"becomeAScratcher.buttons.backToProfile": "Back to Profile Page",
"becomeAScratcher.buttons.finishLater": "Finish Later",
"becomeAScratcher.congratulations.header": "Congratulations, {username}! You have shown that you are ready to become a Scratcher.",
"becomeAScratcher.congratulations.body": "Scratch is a friendly and welcoming community for everyone, where people create, share, and learn together. We welcome people of all ages, races, ethnicities, religions, abilities, sexual orientations, and gender identities.",
"becomeAScratcher.toBeAScratcher.header": "What does it mean to be a Scratcher?",
"becomeAScratcher.toBeAScratcher.body": "You might notice on your profile page that you are currently a “New Scratcher”. Now that you have spent some time on Scratch, we invite you to become a “Scratcher”.",
"becomeAScratcher.toBeAScratcher.definition": "Scratchers have a bit more experience on Scratch and are excited to both contribute to the community and to make it a supportive and welcoming space for others.",
"becomeAScratcher.toBeAScratcher.canDo": "Here are some things Scratchers do:",
"becomeAScratcher.toBeAScratcher.createStudios": "Create studios",
"becomeAScratcher.toBeAScratcher.helpOut": "Help out in the community",
"becomeAScratcher.toBeAScratcher.communityGuidelines": "Next, we will take you through the community guidelines and explain what these are.",
"becomeAScratcher.guidelines.respectSection": "Become a Scratcher - Treat everyone with respect",
"becomeAScratcher.guidelines.respectHeader": "Scratchers treat everyone with respect.",
"becomeAScratcher.guidelines.respectBody": "Everyone on Scratch is encouraged to share things that excite them and are important to them—we hope that you find ways to celebrate your own identity on Scratch, and allow others to do the same.",
"becomeAScratcher.guidelines.safeSection": "Become a Scratcher - Be safe",
"becomeAScratcher.guidelines.safeHeader": "Scratchers are safe: we keep personal and contact information private.",
"becomeAScratcher.guidelines.safeBody": "This includes not sharing real last names, phone numbers, addresses, hometowns, school names, email addresses, usernames or links to social media sites, video chatting applications, or websites with private chat functionality.",
"becomeAScratcher.guidelines.feedbackSection": "Become a Scratcher - Give helpful feedback",
"becomeAScratcher.guidelines.feedbackHeader": "Scratchers give helpful feedback.",
"becomeAScratcher.guidelines.feedbackBody": "When commenting on a project, remember to say something you like about it, offer suggestions, and be kind, not critical.",
"becomeAScratcher.guidelines.remix1Section": "Become a Scratcher - Embrace remix culture",
"becomeAScratcher.guidelines.remix1Header": "Scratchers embrace remix culture.",
"becomeAScratcher.guidelines.remix1Body": "Remixing is when you build upon someone elses projects, code, ideas, images, or anything else they share on Scratch to make your own unique creation.",
"becomeAScratcher.guidelines.remix2Section": "Become a Scratcher - Embrace remix culture",
"becomeAScratcher.guidelines.remix2Header": "Remixing is a great way to collaborate and connect with other Scratchers.",
"becomeAScratcher.guidelines.remix2Body": "You are encouraged to use anything you find on Scratch in your own creations, as long as you provide credit to everyone whose work you used and make a meaningful change to it. ",
"becomeAScratcher.guidelines.remix3Section": "Become a Scratcher - Embrace remix culture",
"becomeAScratcher.guidelines.remix3Header": "Remixing means sharing with others.",
"becomeAScratcher.guidelines.remix3Body": "When you share something on Scratch, you are giving permission to all Scratchers to use your work in their creations, too.",
"becomeAScratcher.guidelines.honestSection": "Become a Scratcher - Be honest",
"becomeAScratcher.guidelines.honestHeader": "Scratchers are honest.",
"becomeAScratcher.guidelines.honestBody": "Its important to be honest and authentic when interacting with others on Scratch, and remember that there is a person behind every Scratch account.",
"becomeAScratcher.guidelines.friendlySection": "Become a Scratcher - Keep the site friendly",
"becomeAScratcher.guidelines.friendlyHeader": "Scratchers help keep the site friendly.",
"becomeAScratcher.guidelines.friendlyBody": "Its important to keep your creations and conversations friendly and appropriate for all ages. If you think something on Scratch is mean, insulting, too violent, or otherwise disruptive to the community, click “Report” to let us know about it.",
"becomeAScratcher.invitation.header": "{username}, we invite you to become a Scratcher.",
"becomeAScratcher.invitation.body": "Scratch is a friendly and welcoming community for everyone. If you agree to be respectful, be safe, give helpful feedback, embrace remix culture, be honest, and help keep the site friendly, click “I agree!”",
"becomeAScratcher.invitation.finishLater": "You get to decide if you want to become a Scratcher. If you do not want to be a Scratcher yet, just click “Finish Later” above.",
"registration.success.error": "Sorry, an unexpected error occurred.",
"becomeAScratcher.success.header": "Hooray! You are now officially a Scratcher.",
"becomeAScratcher.success.body": "Here are some links that might be helpful for you.",
"becomeAScratcher.success.communityGuidelines": "Community Guidelines",
"becomeAScratcher.success.createAProject": "Create a Project",
"becomeAScratcher.noInvitation.header": "Whoops! Looks like you havent received an invitation to become a Scratcher yet.",
"becomeAScratcher.noInvitation.body": "To become a Scratcher, you must be active on Scratch for a while, share several projects, and comment constructively in the community. After a few weeks, you will receive a notification inviting you to become a Scratcher. Scratch on!",
"becomeAScratcher.finishLater.header": "No worries, take your time!",
"becomeAScratcher.finishLater.body": "By leaving this page, you will not finish the process to become a Scratcher and will stay as a New Scratcher. If you change your mind later, you can always come back via your profile page.",
"becomeAScratcher.finishLater.clickBecomeAScratcher": "Just click on “★ Become a Scratcher!” below your username."
}

View file

@ -30,7 +30,7 @@ const AdminMessage = props => (
id="messages.scratcherInvite"
values={{
learnMore: (
<a href={`/users/${props.username}#promote`}>
<a href={`/become-a-scratcher`}>
{props.intl.formatMessage({id: 'messages.learnMore'})}
</a>
)
@ -43,8 +43,7 @@ const AdminMessage = props => (
AdminMessage.propTypes = {
datetimeCreated: PropTypes.string.isRequired,
intl: intlShape,
onDismiss: PropTypes.func.isRequired,
username: PropTypes.string.isRequired
onDismiss: PropTypes.func.isRequired
};
module.exports = injectIntl(AdminMessage);

View file

@ -322,7 +322,6 @@ const MessagesPresentation = props => {
datetimeCreated={props.scratcherInvite.datetime_created}
id={props.scratcherInvite.id}
key={`invite${props.scratcherInvite.id}`}
username={props.user.username}
onDismiss={() => { // eslint-disable-line react/jsx-no-bind
props.onAdminDismiss('invite', props.scratcherInvite.id);
}}
@ -385,7 +384,6 @@ MessagesPresentation.propTypes = {
user: PropTypes.shape({
id: PropTypes.number,
banned: PropTypes.bool,
username: PropTypes.string,
token: PropTypes.string,
thumbnailUrl: PropTypes.string,
dateJoined: PropTypes.string,

View file

@ -0,0 +1 @@
<svg width="173" height="119" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x=".5" width="172" height="119" rx="8" fill="#4C97FF" fill-opacity=".15"/><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="173" height="119"><rect x=".5" width="172" height="119" rx="8" fill="#C4C4C4"/></mask><g mask="url(#a)"><path d="M11.702 93.257C.5 93.257-8.5 83.593-8.5 71.628-8.5 59.665.591 50 11.702 50h7.346c11.203 0 20.202 9.664 20.202 21.629 0 11.964-9.09 21.628-20.202 21.628l6.52-1.565c-1.194 2.854-1.47 4.602-.827 10.492.735 7.363-5.51 8.1-16.436-9.111-.184-.276-1.378-2.117-1.562-2.301l4.96 2.485Z" fill="#4C97FF" fill-opacity=".5"/><path d="M15.929 85.158c-7.911 0-14.325-6.428-14.325-14.358 0-7.93 6.414-14.358 14.325-14.358S30.254 62.87 30.254 70.8c0 7.93-6.414 14.358-14.325 14.358Z" fill="#4C97FF"/><path d="M22.724 73.1s-6.428 4.234-13.315.369" stroke="#3373CC" stroke-width="2" stroke-miterlimit="10"/><path d="M25.112 70.34a1.747 1.747 0 0 1-1.745-1.75c0-.965.781-1.748 1.745-1.748.963 0 1.744.783 1.744 1.749 0 .965-.78 1.748-1.744 1.748ZM6.747 70.34a1.747 1.747 0 0 1-1.745-1.75c0-.965.78-1.748 1.745-1.748.963 0 1.744.783 1.744 1.749 0 .965-.78 1.748-1.744 1.748Z" fill="#3373CC"/><path d="M48.64 16.2c1.025-.273 1.776-1.094 2.049-2.052l.614-2.327c.273-1.095 1.844-1.095 2.117 0l.614 2.327c.273 1.026 1.093 1.779 2.049 2.053l2.32.616c1.093.273 1.093 1.847 0 2.12l-2.32.617c-1.024.274-1.776 1.095-2.049 2.053l-.614 2.326c-.273 1.095-1.844 1.095-2.117 0l-.614-2.326c-.273-1.027-1.093-1.78-2.048-2.053l-2.322-.616c-1.092-.274-1.092-1.848 0-2.121l2.322-.616ZM81.702 90.202a1.39 1.39 0 0 0 .963-.966l.29-1.095c.128-.515.867-.515.996 0l.289 1.095c.128.483.514.837.964.966l1.092.29c.514.129.514.87 0 .998l-1.092.29a1.39 1.39 0 0 0-.964.966l-.29 1.095c-.128.515-.867.515-.995 0l-.29-1.095a1.39 1.39 0 0 0-.963-.966l-1.093-.29c-.514-.128-.514-.87 0-.998l1.093-.29ZM69.64 79.2c1.025-.273 1.776-1.094 2.049-2.052l.614-2.327c.273-1.095 1.844-1.095 2.117 0l.614 2.327c.273 1.026 1.093 1.779 2.048 2.053l2.322.616c1.092.273 1.092 1.847 0 2.12l-2.322.617c-1.024.273-1.775 1.095-2.048 2.053l-.614 2.326c-.273 1.095-1.844 1.095-2.117 0l-.614-2.326c-.273-1.027-1.092-1.78-2.048-2.053l-2.322-.616c-1.092-.274-1.092-1.848 0-2.121l2.322-.616ZM154.886 100.294a1.3 1.3 0 0 0 .903-.905l.271-1.027c.121-.483.814-.483.934 0l.271 1.027c.121.453.482.785.904.905l1.024.272c.482.121.482.815 0 .936l-1.024.272c-.452.12-.783.483-.904.905l-.271 1.027c-.12.483-.813.483-.934 0l-.271-1.027a1.301 1.301 0 0 0-.903-.905l-1.025-.272c-.481-.121-.481-.815 0-.936l1.025-.272Z" fill="#fff"/><path d="M168.979 63.726c11.826 0 21.37-10.228 21.37-22.863S180.745 18 168.979 18H107.93C96.105 17.94 86.5 28.168 86.5 40.803c0 12.635 9.605 22.863 21.37 22.863h5.103c1.26 3.068.3 8.423-.901 14.62-1.621 8.303 15.668 4.332 27.193-13.898.18-.24.3-.481.48-.662h29.234Z" fill="#4C97FF" fill-opacity=".5"/><path fill-rule="evenodd" clip-rule="evenodd" d="M136.623 37.193c-.84 9.446-11.465 16.846-14.167 16.846-2.701 0-13.326-7.4-14.166-16.846 0-.3-.061-.602-.061-.902 0-4.573 3.722-8.243 8.284-8.243 2.342 0 4.443.963 5.943 2.527a8.19 8.19 0 0 1 5.943-2.527c4.562 0 8.284 3.73 8.284 8.243 0 .3-.06.601-.06.902ZM169.1 38.336c-.6 6.618-8.044 11.792-9.965 11.792-1.921 0-9.364-5.174-9.965-11.792 0-.24-.06-.421-.06-.662 0-3.189 2.642-5.836 5.823-5.836 1.621 0 3.122.662 4.142 1.745 1.021-1.083 2.521-1.745 4.142-1.745a5.797 5.797 0 0 1 5.823 5.836c.12.24.06.481.06.662Z" fill="#4C97FF"/></g></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 406 KiB

View file

@ -0,0 +1 @@
<svg width="172" height="119" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="172" height="119" rx="8" fill="#FFBF00" opacity=".3"/><path d="M74 37.886C54 5.978 16.333 10.156 0 16.234v46.153c5.5-2.469 23.9.114 53.5 30.199 29.6 30.084 91.333 18.613 118.5 9.116v-51.85c-24.333 9.306-78 19.942-98-11.966Z" fill="#fff" fill-opacity=".5"/><path stroke="#4C97FF" d="M47.5 12.5h85v73h-85z"/><path d="M139.278 34.194c-1.936 3.031-3.59 4.9-5.121 5.83a.675.675 0 0 1-.932-.235 8.511 8.511 0 0 0-2.761-2.876 9.392 9.392 0 0 0-3.845-1.334.674.674 0 0 1-.588-.76c.238-1.793 1.243-4.078 3.141-7.05 5.518-8.692 14.295-17.459 17.098-15.668 2.798 1.787-1.442 13.404-6.992 22.093Z" fill="#825331"/><path d="M127.281 50.23a.669.669 0 0 1-.23.098l-.296.064c-3.971 1.027-7.805-.141-10.589-2.614-1.104-.98-1.911-2.082-2.204-2.996-.388-1.212.203-2.166 1.498-2.014 2.039.233 3.391-.193 4.238-.973.242-.224.263-.252.477-.593 1.892-2.807 5.741-3.656 8.643-1.902 2.921 1.796 3.759 5.59 1.854 8.419a6.254 6.254 0 0 1-3.391 2.51Z" fill="#E4B681"/><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="113" y="38" width="19" height="13"><path d="M127.281 50.23a.669.669 0 0 1-.23.098l-.296.064c-3.971 1.027-7.805-.141-10.589-2.614-1.104-.98-1.911-2.082-2.204-2.996-.388-1.212.203-2.166 1.498-2.014 2.039.233 3.391-.193 4.238-.973.242-.224.263-.252.477-.593 1.892-2.807 5.741-3.656 8.643-1.902 2.921 1.796 3.759 5.59 1.854 8.419a6.254 6.254 0 0 1-3.391 2.51Z" fill="#E4B681"/></mask><g mask="url(#a)"><path d="M124 41.5c.4-2.4-2.833-2-4.5-1.5-2.833.334-8.7 2.1-9.5 6.5-1 5.5 15 6 18.5 5s1-4.5-2-4.5-3-2.5-2.5-5.5Z" fill="#5CB1D6"/></g><path d="M106.089 50.197c6.045 2.221-10.353 9.187-17.64 8.098-5.288-.79-7.343-4.372-7.342-6.748 0-2.97 10.394-5.36 10.992-1.9.599 3.46 7.945-1.671 13.99.55ZM80.728 38.767c.744-.003 1.395.197 1.846.495.455.3.659.663.66.996.001.333-.2.697-.653 1-.448.302-1.098.506-1.842.51-.743.002-1.394-.197-1.846-.496-.454-.3-.658-.662-.66-.995 0-.334.2-.697.653-1.001.45-.302 1.099-.506 1.842-.509ZM83.967 65.533c7.582 1.726-16.067 15.524-25.585 15.704-6.906.13-8.814-4.479-8.249-7.859.706-4.225 13.328-.305 15.197-5.31 2.975-7.965 11.055-4.262 18.637-2.535Z" fill="#5CB1D6" stroke="#4C97FF"/><rect x="45" y="10" width="5" height="5" rx="2.5" fill="#4C97FF"/><rect x="44.5" y="9.5" width="6" height="6" rx="3" stroke="#000" stroke-opacity=".1"/><rect x="130" y="10" width="5" height="5" rx="2.5" fill="#4C97FF"/><rect x="129.5" y="9.5" width="6" height="6" rx="3" stroke="#000" stroke-opacity=".1"/><rect x="130" y="83" width="5" height="5" rx="2.5" fill="#4C97FF"/><rect x="129.5" y="82.5" width="6" height="6" rx="3" stroke="#000" stroke-opacity=".1"/><path fill-rule="evenodd" clip-rule="evenodd" d="m99.134 84.247-6.635 22.577a2.876 2.876 0 0 1-3.598 1.964l-29.857-8.774a2.977 2.977 0 0 0-2.204.241l-5.08 2.773a2.982 2.982 0 0 1-2.204.241l-14.56-4.279a2.978 2.978 0 0 1-1.723-1.395l-2.773-5.08a2.977 2.977 0 0 0-1.722-1.395l-7.362-2.163a2.875 2.875 0 0 1-1.964-3.599l6.634-22.577a2.875 2.875 0 0 1 3.599-1.964l7.28 2.14c.736.216 1.376.76 1.723 1.395l2.83 5.186c.428.659.987 1.178 1.723 1.395L57.8 75.21a2.977 2.977 0 0 0 2.204-.24l5.186-2.831c.717-.323 1.467-.458 2.203-.241l29.776 8.75a2.875 2.875 0 0 1 1.964 3.598Z" fill="#CF63CF"/><path fill-rule="evenodd" clip-rule="evenodd" d="m97.639 98.766-3.041 3.372c-.609.679-1.74.252-1.74-.658V86.19c0-.86 1.033-1.31 1.672-.726l11.289 10.306c.669.61.234 1.715-.677 1.715h-4.586l1.815 4.045a1.615 1.615 0 0 1-.792 2.127c-.8.365-1.743.008-2.104-.801l-1.836-4.09Z" fill="#575E75"/><path d="m97.639 98.766.353-.159-.243-.542-.398.441.288.26Zm-3.041 3.372-.288-.259v.001l.288.258Zm-.068-16.674-.261.286.261-.286Zm11.289 10.306-.261.286.261-.286Zm-5.263 1.715v-.387h-.598l.245.546.353-.159Zm1.815 4.045.354-.158-.354.158Zm-.792 2.127.161.352h.001l-.162-.352Zm-2.104-.801.354-.158-.354.158Zm-2.124-4.35-3.04 3.373.575.519 3.04-3.373-.575-.519Zm-3.042 3.374c-.375.418-1.064.148-1.064-.4h-.775c0 1.271 1.574 1.856 2.416.917l-.577-.517Zm-1.064-.4V86.19h-.775v15.29h.775Zm0-15.29c0-.517.628-.801 1.024-.44l.522-.572c-.882-.806-2.321-.193-2.321 1.012h.775Zm1.024-.44 11.289 10.306.522-.572-11.289-10.306-.522.572Zm11.289 10.306a.6.6 0 0 1-.416 1.042v.775c1.256 0 1.875-1.536.938-2.39l-.522.573Zm-.416 1.042h-4.586v.775h4.586v-.775Zm-4.939.546 1.814 4.045.708-.317-1.815-4.046-.707.318Zm1.814 4.045a1.227 1.227 0 0 1-.6 1.616l.324.704a2.003 2.003 0 0 0 .984-2.637l-.708.317Zm-.599 1.615a1.195 1.195 0 0 1-1.59-.606l-.707.316a1.97 1.97 0 0 0 2.619.995l-.322-.705Zm-1.59-.606-1.836-4.091-.707.317 1.836 4.091.707-.317Z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -0,0 +1 @@
<svg width="95" height="95" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M23.09 75.976c12.294 2.295 51.231-3.621 51.231-3.621 3.72-.931 8.847-1.86 7.78-3.102-7.477-14.155-7.707-29.918-11.976-44.98a.993.993 0 0 0-1.035-.716l-56.534 4.489c0 7.504 1.55 43.6 10.534 47.93Z" fill="#E5F0FF"/><path opacity=".25" fill-rule="evenodd" clip-rule="evenodd" d="M27.366 72.193c10.419 1.16 42.921-3.357 42.921-3.357 3.186-.651 7.57-1.33 6.687-2.073-4.525-8.55-5.344-18.657-6.553-27.214a.995.995 0 0 0-1.067-.85l-51.917 4.18c0 4.964 2.37 26.832 9.93 29.314Z" fill="#4C97FF"/><path d="M66.168 31.144C65 25.897 64.511 22.49 64.23 17.416c-.025-.452-.612-.619-.866-.244a.47.47 0 0 1-.712.078l-.503-.474a.5.5 0 0 0-.774.108l-.315.533a.5.5 0 0 1-.774.108l-.45-.424a.5.5 0 0 0-.773.108l-.33.556a.5.5 0 0 1-.757.123l-.245-.212a.495.495 0 0 0-.82.352c-.253 5.718.295 9.234 2.015 14.654.13.409.682.47.9.101l.2-.335a.5.5 0 0 1 .772-.109l.45.425a.5.5 0 0 0 .774-.108l.316-.533a.5.5 0 0 1 .773-.109l.45.425a.5.5 0 0 0 .774-.108l.35-.591a.475.475 0 0 1 .712-.122c.35.29.87-.03.771-.474ZM27.21 34.602c-.707-5.329-.897-8.766-.735-13.845.014-.452-.556-.67-.841-.318a.47.47 0 0 1-.717.015l-.46-.516a.5.5 0 0 0-.78.04l-.36.504a.5.5 0 0 1-.78.04l-.412-.462a.5.5 0 0 0-.78.04l-.376.526a.5.5 0 0 1-.766.056l-.225-.232a.495.495 0 0 0-.847.279c-.75 5.674-.51 9.224.73 14.773a.496.496 0 0 0 .888.18l.227-.317a.5.5 0 0 1 .78-.04l.411.462a.5.5 0 0 0 .78-.04l.361-.504a.5.5 0 0 1 .78-.04l.412.462a.5.5 0 0 0 .78-.04l.4-.559a.474.474 0 0 1 .72-.06c.322.321.87.047.81-.404Z" fill="#4C97FF" fill-opacity=".25"/><path fill-rule="evenodd" clip-rule="evenodd" d="M48.118 62.264c3.886-.477 6.86-2.401 6.409-6.077-.112-.908-.369-1.656-.758-2.261l-.534-5.325c-.072-.587-.807-.837-1.252-.428l-2.736 2.507c-.737-.264-1.628-.311-2.575-.195-.944.116-1.782.375-2.433.81l-3.262-1.77c-.545-.288-1.198.133-1.111.718l.77 5.295c-.233.681-.3 1.47-.188 2.378.451 3.675 3.786 4.825 7.67 4.348Zm1.2-2.444a1.997 1.997 0 0 0 1.412-.84c.182-.234.11-.567-.149-.734a.587.587 0 0 0-.774.168.88.88 0 0 1-.618.358c-.472.058-.916-.256-.972-.718l-.095-.768c.83-.329 1.396-1.092 1.34-1.54-.068-.56-.999-.445-2.122-.308-1.136.14-2.052.252-1.983.811.055.449.76 1.056 1.656 1.159l.096.782c.056.462-.282.872-.756.93a.886.886 0 0 1-.699-.196.585.585 0 0 0-.792.024.514.514 0 0 0 .034.748c.42.374.995.545 1.586.472a2.085 2.085 0 0 0 1.348-.746c.399.306.927.467 1.488.398Z" fill="#E5F0FF"/></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

View file

@ -0,0 +1 @@
<svg width="95" height="95" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M68.4 47.08c0 10.088-10.014 18.254-22.4 18.254-1.598 0-3.171-.125-4.67-.4l-8.647 4.682c-1.569.849-2.607.124-2.32-1.623l1.153-7.004c-4.844-3.321-7.916-8.316-7.916-13.91C23.6 36.992 33.64 28.8 46 28.8c12.386 0 22.4 8.19 22.4 18.28Z" fill="#FFAB19" opacity=".5"/><path fill-rule="evenodd" clip-rule="evenodd" d="m47.06 36.963 2.351 4.004 4.633.334c1.578.113 2.34 1.986 1.292 3.17l-3.084 3.475 1.115 4.51c.38 1.534-1.167 2.839-2.616 2.206l-4.257-1.858-3.944 2.454c-1.344.835-3.06-.232-2.909-1.806l.453-4.623-3.552-2.992c-1.21-1.02-.727-2.984.819-3.324l4.537-1 1.747-4.302c.597-1.465 2.612-1.611 3.414-.248Z" fill="#fff" fill-opacity=".5"/><path fill-rule="evenodd" clip-rule="evenodd" d="m75.59 64.952.55 2.494 2.366.962a1.05 1.05 0 0 1 .138 1.878L76.44 71.58l-.182 2.548a1.05 1.05 0 0 1-1.744.71l-1.91-1.695-2.48.614a1.05 1.05 0 0 1-1.216-1.439l1.022-2.341-1.35-2.169a1.05 1.05 0 0 1 .993-1.6l2.544.248 1.644-1.954a1.05 1.05 0 0 1 1.828.45Z" fill="#FFAB19" opacity=".5"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -0,0 +1 @@
<svg width="182" height="171" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0 68.458h181.14v96.55a5.06 5.06 0 0 1-1.479 3.571 5.041 5.041 0 0 1-3.571 1.479H5.05a5.05 5.05 0 0 1-5.05-5.05v-96.55Z" fill="#2E8EB8"/><path d="m0 68.458 87.52-66.42a5 5 0 0 1 6.1 0l87.52 66.42H0Z" fill="#47A8D1"/><path fill-rule="evenodd" clip-rule="evenodd" d="M89.408 95.314c8.182 0 14.807 6.623 14.807 14.803 0 7.97-6.625 14.593-14.807 14.593-8.156 0-14.623-6.623-14.623-14.593 0-8.18 6.467-14.803 14.623-14.803ZM83.94 85.286a5.894 5.894 0 0 1-5.912-5.7c0-.554-.027-1.16-.027-1.768 0-7.995 3.247-16.65 12.96-23.934l8.657-6.465c4.593-3.404 6.097-7.494 6.097-11.927 0-6.623-4.909-13.062-15.124-13.062-9.845 0-15.625 7.995-15.625 15.965v.131c0 3.352-2.719 6.043-6.07 5.911L56.7 44.015c-3.088-.105-5.543-2.639-5.675-5.726 0-.554-.026-1.082-.026-1.61C51 16.81 65.78 0 90.591 0c27.53 0 41.307 16.492 41.307 33.46 0 13.616-6.308 22.931-16.496 30.584l-6.44 4.75c-4.751 3.562-8.314 7.098-9.555 12.27-.633 2.56-3.088 4.222-5.727 4.222h-9.74Z" fill="#FFBF00"/><path d="M0 115.578h181.14v49.43a5.06 5.06 0 0 1-1.479 3.571 5.041 5.041 0 0 1-3.571 1.479H5.05a5.05 5.05 0 0 1-5.05-5.05v-49.43Z" fill="#47A8D1"/><path d="m0 68.458 64.44 41.32a4.788 4.788 0 0 1 .24 7.89L0 164.988v-96.53ZM181.141 68.458l-64.44 41.32a4.796 4.796 0 0 0-2.195 3.882 4.792 4.792 0 0 0 1.955 4.008l64.68 47.32v-96.53Z" fill="#5CB1D6"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="m4 10.807 5.21-5.21a1.419 1.419 0 0 1 1.544-.286c.516.222.835.713.835 1.282V8.57l7.25 1.007a2.264 2.264 0 0 1 1.937 2.542c-.161 1.025-.961 1.817-1.945 1.941l-7.242 1.034v1.89c0 .575-.338 1.088-.861 1.306-.526.217-1.108.1-1.518-.31L4 12.768a1.381 1.381 0 0 1-.4-.981c0-.373.143-.722.4-.98Z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 433 B

View file

@ -0,0 +1 @@
<svg width="69" height="79" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m67.5 22-50-20m50 41H2m65.5 18.5L31 77" stroke="#FFBF00" stroke-width="3" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 188 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="m20.394 12.784-5.21 5.21a1.419 1.419 0 0 1-1.544.287 1.372 1.372 0 0 1-.835-1.283v-1.976l-7.25-1.007a2.265 2.265 0 0 1-1.937-2.542c.161-1.025.961-1.816 1.945-1.94l7.242-1.034v-1.89c0-.576.339-1.089.861-1.306a1.374 1.374 0 0 1 1.518.31l5.21 5.21c.257.259.4.607.4.98 0 .374-.143.722-.4.981Z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 433 B

View file

@ -0,0 +1 @@
<svg width="69" height="79" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2 22 52 2M2 43h65.5M2 61.5 38.5 77" stroke="#FFBF00" stroke-width="3" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 185 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -0,0 +1,97 @@
/* eslint-disable max-len */
const React = require('react');
const {mountWithIntl} = require('../../helpers/intl-helpers.jsx');
import {ConnectedBecomeAScratcher as BecomeAScratcherPage} from '../../../src/views/become-a-scratcher/become-a-scratcher.jsx';
import sessionActions from '../../../src/redux/session.js';
import configureStore from 'redux-mock-store';
jest.mock('react-dom', () => ({
render: jest.fn()
}));
jest.mock('../../../src/components/modal/base/modal.jsx', () => () => 'MockModal');
describe('BecomeAScratcherPage', () => {
const mockStore = configureStore();
test('Display 404 when no user', () => {
const NotLoggedInUserStore = mockStore({
session: {
status: sessionActions.Status.FETCHED,
session: {
user: null,
permissions: {}
}
}
});
const component = mountWithIntl(
<BecomeAScratcherPage />, {context: {store: NotLoggedInUserStore}}
);
expect(component.find('div.not-available-outer').exists()).toBeTruthy();
});
test('Display No Invitation when user is not invited', () => {
const NotInvitedUserStore = mockStore({
session: {
status: sessionActions.Status.FETCHED,
session: {
user: {
id: 123,
thumbnailUrl: 'test',
username: 'test123'
},
permissions: {}
}
}
});
const component = mountWithIntl(
<BecomeAScratcherPage />, {context: {store: NotInvitedUserStore}}
);
expect(component.find('div.no-invitation').exists()).toBeTruthy();
});
test('Display Onboarding when user is invited', () => {
const InvitedUserStore = mockStore({
session: {
status: sessionActions.Status.FETCHED,
session: {
user: {
id: 123,
thumbnailUrl: 'test',
username: 'test123'
},
permissions: {
invited_scratcher: true
}
}
}
});
const component = mountWithIntl(
<BecomeAScratcherPage />, {context: {store: InvitedUserStore}}
);
expect(component.find('div.congratulations-page').exists()).toBeTruthy();
});
test('Display celebration page when user is already a scratcher', () => {
const AlreadyScratcherStore = mockStore({
session: {
status: sessionActions.Status.FETCHED,
session: {
user: {
id: 123,
thumbnailUrl: 'test',
username: 'test123'
},
permissions: {
scratcher: true
}
}
}
});
const component = mountWithIntl(
<BecomeAScratcherPage />, {context: {store: AlreadyScratcherStore}}
);
expect(component.find('div.hooray-screen').exists()).toBeTruthy();
});
});