mirror of
https://github.com/anyproto/anytype-ts.git
synced 2025-06-08 05:57:02 +09:00
JS-5569: email collection tooltip
This commit is contained in:
parent
22c852fe3d
commit
9b06e13eac
14 changed files with 348 additions and 11 deletions
|
@ -17,7 +17,7 @@ const { fixPathForAsarUnpack, is } = require('electron-util');
|
|||
|
||||
const APP_NAME = 'com.anytype.desktop';
|
||||
const MANIFEST_FILENAME = `${APP_NAME}.json`;
|
||||
const EXTENSION_IDS = [ 'jbnammhjiplhpjfncnlejjjejghimdkf', 'jkmhmgghdjjbafmkgjmplhemjjnkligf' ];
|
||||
const EXTENSION_IDS = [ 'jbnammhjiplhpjfncnlejjjejghimdkf', 'jkmhmgghdjjbafmkgjmplhemjjnkligf', 'lcamkcmpcofgmbmloefimnelnjpcdpfn' ];
|
||||
const USER_PATH = app.getPath('userData');
|
||||
const EXE_PATH = app.getPath('exe');
|
||||
|
||||
|
@ -191,4 +191,4 @@ const writeManifest = (dst, data) => {
|
|||
|
||||
};
|
||||
|
||||
module.exports = { installNativeMessagingHost };
|
||||
module.exports = { installNativeMessagingHost };
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
export default {
|
||||
clipper: {
|
||||
ids: [ 'jbnammhjiplhpjfncnlejjjejghimdkf', 'jkmhmgghdjjbafmkgjmplhemjjnkligf' ],
|
||||
ids: [ 'jbnammhjiplhpjfncnlejjjejghimdkf', 'jkmhmgghdjjbafmkgjmplhemjjnkligf', 'lcamkcmpcofgmbmloefimnelnjpcdpfn' ],
|
||||
name: 'Anytype Webclipper',
|
||||
prefix: 'anytypeWebclipper',
|
||||
emojiUrl: 'https://anytype-static.fra1.cdn.digitaloceanspaces.com/emojies/'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -170,6 +170,7 @@
|
|||
"commonProgress": "Progress",
|
||||
"commonMainChat": "Chat",
|
||||
"commonMenu": "Menu",
|
||||
"commonSignUp": "Sign Up",
|
||||
|
||||
"pluralDay": "day|days",
|
||||
"pluralObject": "Object|Objects",
|
||||
|
@ -1849,6 +1850,18 @@
|
|||
"onboardingObjectCreationStart21": "<b>Choose here from the most popular object types</b>, such as Page, Task, or Collection. You can also select an object from the Type menu.",
|
||||
"onboardingObjectCreationStart2Button": "I got it!",
|
||||
|
||||
"emailCollectionStep0Title": "Want to stay in touch?",
|
||||
"emailCollectionStep0Description": "Enter your email to receive tips and updates. We do not link your account with your email, ever. Cancel anytime.",
|
||||
"emailCollectionStep1Title": "Just a moment",
|
||||
"emailCollectionStep1Description": "Enter the code we’ve sent you to your email",
|
||||
"emailCollectionStep2Title": "You’re Subscribed!",
|
||||
"emailCollectionStep2Description": "You are now set to receive the latest updates and perks. Enjoy exploring!",
|
||||
|
||||
"emailCollectionCheckboxTipsLabel": "Insider tips & tutorials on using Anytype",
|
||||
"emailCollectionCheckboxNewsLabel": "Product updates & company news",
|
||||
"emailCollectionEnterEmail": "Enter email...",
|
||||
"emailCollectionGreat": "Great!",
|
||||
|
||||
"onboardingObjectCreationFinish": "Creating Objects",
|
||||
"onboardingObjectCreationFinish11": "<b>For the Object you created, you can adjust it using the top menu.</b> Change the cover, layout, or set up a relations to build the graph.",
|
||||
"onboardingObjectCreationFinish1Button": "Ok! I like it",
|
||||
|
|
|
@ -30,4 +30,6 @@
|
|||
@import "./share";
|
||||
|
||||
@import "./preview/common";
|
||||
@import "./media/common";
|
||||
@import "./media/common";
|
||||
|
||||
@import "./emailCollectionForm";
|
||||
|
|
38
src/scss/component/emailCollectionForm.scss
Normal file
38
src/scss/component/emailCollectionForm.scss
Normal file
|
@ -0,0 +1,38 @@
|
|||
@import "~scss/_mixins";
|
||||
|
||||
.emailCollectionForm { display: flex; flex-direction: column; }
|
||||
.emailCollectionForm {
|
||||
.statusBar { @include text-small; min-height: 18px; margin: 0px 0px 2px; padding-top: 4px; color: var(--color-text-secondary); }
|
||||
.statusBar.error { color: var(--color-red); }
|
||||
|
||||
.buttonWrapper { padding-top: 8px; }
|
||||
.buttonWrapper {
|
||||
.button { width: 100%; }
|
||||
}
|
||||
|
||||
.step0 { padding-top: 16px; }
|
||||
.step0 {
|
||||
.check { @include text-small; }
|
||||
.inputWrapper { padding-top: 16px; }
|
||||
.inputWrapper {
|
||||
.input { border-radius: 3px; height: 36px; }
|
||||
}
|
||||
}
|
||||
|
||||
.step1 { padding-top: 16px; text-align: center; }
|
||||
.step1 {
|
||||
.pin { margin-bottom: 8px; }
|
||||
.pin {
|
||||
.input { @include text-header1; width: 35px; height: 47px; border-radius: 6px; border-color: var(--color-shape-primary); }
|
||||
}
|
||||
|
||||
.resend { font-weight: 500; color: var(--color-text-secondary); @include text-small; }
|
||||
.resend.countdown { color: var(--color-text-primary); }
|
||||
.resend:not(.countdown):hover { color: var(--color-control-active); }
|
||||
}
|
||||
|
||||
.step2 { padding-top: 8px; }
|
||||
.step2 {
|
||||
.icon { width: 100%; height: 138px; background: url('~img/icon/payment/green.svg') 50% 50% no-repeat; background-size: 80px; }
|
||||
}
|
||||
}
|
|
@ -74,6 +74,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.menu.menuOnboarding.invertedColor { box-shadow: inset 0px 0px 0px 1px var(--color-control-inactive), 0px 4px 12px 0px rgba(37, 37, 37, 0.20); background-color: var(--color-bg-primary); color: var(--color-text-primary); }
|
||||
|
||||
.menu.menuOnboarding.isWizard { min-height: 458px; }
|
||||
.menu.menuOnboarding.isWizard {
|
||||
.bottom { position: absolute; bottom: 16px; width: calc(100% - 32px); margin: 0px; }
|
||||
|
|
|
@ -220,6 +220,10 @@ class App extends React.Component<object, State> {
|
|||
|
||||
componentDidMount () {
|
||||
this.init();
|
||||
|
||||
window.setTimeout(() => {
|
||||
Onboarding.start('emailCollection', false, true);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
init () {
|
||||
|
|
|
@ -94,6 +94,8 @@ import ShareTooltip from './util/share/tooltip';
|
|||
import ShareBanner from './util/share/banner';
|
||||
import FooterAuthDisclaimer from './footer/auth/disclaimer';
|
||||
|
||||
import EmailCollectionForm from './util/emailCollectionForm';
|
||||
|
||||
export {
|
||||
Page,
|
||||
EditorPage,
|
||||
|
@ -184,5 +186,7 @@ export {
|
|||
ProgressBar,
|
||||
ShareTooltip,
|
||||
ShareBanner,
|
||||
FooterAuthDisclaimer
|
||||
FooterAuthDisclaimer,
|
||||
|
||||
EmailCollectionForm,
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||
import $ from 'jquery';
|
||||
import raf from 'raf';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Button, Icon, Label } from 'Component';
|
||||
import { Button, Icon, Label, EmailCollectionForm } from 'Component';
|
||||
import { I, C, S, U, J, Onboarding, analytics, keyboard, translate } from 'Lib';
|
||||
import ReactCanvasConfetti from 'react-canvas-confetti';
|
||||
|
||||
|
@ -37,6 +37,7 @@ const MenuOnboarding = observer(class MenuSelect extends React.Component<I.Menu,
|
|||
const item = items[current];
|
||||
const l = items.length;
|
||||
const withSteps = l > 1;
|
||||
const withEmailForm = key == 'emailCollection';
|
||||
|
||||
let buttons = [];
|
||||
let category = '';
|
||||
|
@ -88,6 +89,9 @@ const MenuOnboarding = observer(class MenuSelect extends React.Component<I.Menu,
|
|||
loop={true}
|
||||
/>
|
||||
) : ''}
|
||||
{withEmailForm ? (
|
||||
<EmailCollectionForm onStepChange={this.props.position} onComplete={() => this.props.close()} />
|
||||
) : ''}
|
||||
|
||||
<div className={[ 'bottom', withSteps ? 'withSteps' : '' ].join(' ')}>
|
||||
{withSteps ? (
|
||||
|
|
|
@ -188,7 +188,7 @@ const PopupMembershipPageCurrent = observer(class PopupMembershipPageCurrent ext
|
|||
|
||||
this.refButton.setLoading(true);
|
||||
|
||||
C.MembershipGetVerificationEmail(this.refEmail.getValue(), true, (message) => {
|
||||
C.MembershipGetVerificationEmail(this.refEmail.getValue(), true, false, false, (message) => {
|
||||
this.refButton.setLoading(false);
|
||||
|
||||
if (message.error.code) {
|
||||
|
|
|
@ -138,7 +138,7 @@ const PopupMembershipPageFree = observer(class PopupMembershipPageFree extends R
|
|||
|
||||
this.refButton.setLoading(true);
|
||||
|
||||
C.MembershipGetVerificationEmail(this.refEmail.getValue(), this.refCheckbox?.getValue(), (message) => {
|
||||
C.MembershipGetVerificationEmail(this.refEmail.getValue(), this.refCheckbox?.getValue(), false, false, (message) => {
|
||||
this.refButton.setLoading(false);
|
||||
|
||||
if (message.error.code) {
|
||||
|
|
249
src/ts/component/util/emailCollectionForm.tsx
Normal file
249
src/ts/component/util/emailCollectionForm.tsx
Normal file
|
@ -0,0 +1,249 @@
|
|||
import * as React from 'react';
|
||||
import { Label, Checkbox, Input, Button, Icon, Pin } from 'Component';
|
||||
import { analytics, C, I, J, S, translate, U } from 'Lib';
|
||||
|
||||
interface Props {
|
||||
onStepChange: () => void;
|
||||
onComplete: () => void;
|
||||
};
|
||||
|
||||
interface State {
|
||||
countdown: number;
|
||||
status: string;
|
||||
statusText: string;
|
||||
email: string,
|
||||
subscribeNews: boolean,
|
||||
subscribeTips: boolean,
|
||||
};
|
||||
|
||||
class EmailCollectionForm extends React.Component<Props, State> {
|
||||
|
||||
state = {
|
||||
status: '',
|
||||
statusText: '',
|
||||
countdown: 60,
|
||||
email: '',
|
||||
subscribeNews: false,
|
||||
subscribeTips: false,
|
||||
};
|
||||
|
||||
step = 0;
|
||||
node: any = null;
|
||||
refCheckboxTips: any = null;
|
||||
refCheckboxNews: any = null;
|
||||
refEmail: any = null;
|
||||
refButton: any = null;
|
||||
refCode: any = null;
|
||||
|
||||
interval = null;
|
||||
timeout = null;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.onCheck = this.onCheck.bind(this);
|
||||
this.onSubmitEmail = this.onSubmitEmail.bind(this);
|
||||
this.verifyEmail = this.verifyEmail.bind(this);
|
||||
this.onConfirmEmailCode = this.onConfirmEmailCode.bind(this);
|
||||
this.onResend = this.onResend.bind(this);
|
||||
this.validateEmail = this.validateEmail.bind(this);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { status, statusText, countdown } = this.state;
|
||||
|
||||
let content = null;
|
||||
|
||||
switch (this.step) {
|
||||
case 0: {
|
||||
content = (
|
||||
<div className="step step0">
|
||||
<form onSubmit={this.onSubmitEmail}>
|
||||
<div className="check" onClick={() => this.onCheck(this.refCheckboxTips)}>
|
||||
<Checkbox ref={ref => this.refCheckboxTips = ref} value={false} /> {translate('emailCollectionCheckboxTipsLabel')}
|
||||
</div>
|
||||
<div className="check" onClick={() => this.onCheck(this.refCheckboxNews)}>
|
||||
<Checkbox ref={ref => this.refCheckboxNews = ref} value={false} /> {translate('emailCollectionCheckboxNewsLabel')}
|
||||
</div>
|
||||
|
||||
<div className="inputWrapper">
|
||||
<Input ref={ref => this.refEmail = ref} onKeyUp={this.validateEmail} placeholder={translate(`emailCollectionEnterEmail`)} />
|
||||
</div>
|
||||
|
||||
{status ? <div className={[ 'statusBar', status ].join(' ')}>{statusText}</div> : ''}
|
||||
|
||||
<div className="buttonWrapper">
|
||||
<Button ref={ref => this.refButton = ref} onClick={this.onSubmitEmail} className="c36" text={translate('commonSignUp')} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
};
|
||||
|
||||
case 1: {
|
||||
content = (
|
||||
<div className="step step1">
|
||||
<Pin
|
||||
ref={ref => this.refCode = ref}
|
||||
pinLength={4}
|
||||
isVisible={true}
|
||||
onSuccess={this.onConfirmEmailCode}
|
||||
/>
|
||||
|
||||
{status ? <div className={[ 'statusBar', status ].join(' ')}>{statusText}</div> : ''}
|
||||
|
||||
<div onClick={this.onResend} className={[ 'resend', (countdown ? 'countdown' : '') ].join(' ')}>
|
||||
{translate('popupMembershipResend')}
|
||||
{countdown ? U.Common.sprintf(translate('popupMembershipCountdown'), countdown) : ''}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
};
|
||||
|
||||
case 2: {
|
||||
content = (
|
||||
<div className="step step2">
|
||||
<Icon />
|
||||
|
||||
<div className="buttonWrapper">
|
||||
<Button onClick={this.props.onComplete} className="c36" text={translate('emailCollectionGreat')} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="emailCollectionForm">
|
||||
<Label className="category" text={translate(`emailCollectionStep${this.step}Title`)} />
|
||||
<Label className="descr" text={translate(`emailCollectionStep${this.step}Description`)} />
|
||||
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
this.refButton?.setDisabled(true);
|
||||
};
|
||||
|
||||
onCheck (ref) {
|
||||
ref.toggle();
|
||||
};
|
||||
|
||||
setStatus (status: string, statusText: string) {
|
||||
this.setState({ status, statusText });
|
||||
|
||||
window.clearTimeout(this.timeout);
|
||||
this.timeout = window.setTimeout(() => this.clearStatus(), 4000);
|
||||
};
|
||||
|
||||
clearStatus () {
|
||||
this.setState({ status: '', statusText: '' });
|
||||
};
|
||||
|
||||
validateEmail () {
|
||||
this.clearStatus();
|
||||
|
||||
window.clearTimeout(this.timeout);
|
||||
this.timeout = window.setTimeout(() => {
|
||||
const value = this.refEmail?.getValue();
|
||||
const isValid = U.Common.checkEmail(value);
|
||||
|
||||
if (value && !isValid) {
|
||||
this.setStatus('error', translate('errorIncorrectEmail'));
|
||||
};
|
||||
|
||||
this.refButton?.setDisabled(!isValid);
|
||||
}, J.Constant.delay.keyboard);
|
||||
};
|
||||
|
||||
onSubmitEmail (e: any) {
|
||||
if (!this.refButton || !this.refEmail) {
|
||||
return;
|
||||
};
|
||||
|
||||
if (this.refButton.isDisabled()) {
|
||||
return;
|
||||
};
|
||||
|
||||
this.setState({
|
||||
email: this.refEmail.getValue(),
|
||||
subscribeNews: this.refCheckboxNews?.getValue(),
|
||||
subscribeTips: this.refCheckboxTips?.getValue(),
|
||||
}, () => {
|
||||
this.refButton.setLoading(true);
|
||||
this.verifyEmail(e)
|
||||
});
|
||||
};
|
||||
|
||||
verifyEmail (e: any) {
|
||||
e.preventDefault();
|
||||
|
||||
const { email, subscribeNews, subscribeTips } = this.state;
|
||||
|
||||
C.MembershipGetVerificationEmail(email, subscribeNews, subscribeTips, true, (message) => {
|
||||
this.refButton?.setLoading(false);
|
||||
|
||||
if (message.error.code) {
|
||||
this.setStatus('error', message.error.description);
|
||||
return;
|
||||
};
|
||||
|
||||
this.step = 1;
|
||||
this.startCountdown(60);
|
||||
this.forceUpdate();
|
||||
this.props.onStepChange();
|
||||
});
|
||||
};
|
||||
|
||||
onConfirmEmailCode () {
|
||||
const code = this.refCode.getValue();
|
||||
|
||||
C.MembershipVerifyEmailCode(code, (message) => {
|
||||
if (message.error.code) {
|
||||
this.setStatus('error', message.error.description);
|
||||
this.refCode.reset();
|
||||
return;
|
||||
};
|
||||
|
||||
this.step = 2;
|
||||
this.forceUpdate();
|
||||
this.props.onStepChange();
|
||||
});
|
||||
};
|
||||
|
||||
onResend (e: any) {
|
||||
if (!this.state.countdown) {
|
||||
this.verifyEmail(e);
|
||||
};
|
||||
};
|
||||
|
||||
startCountdown (seconds) {
|
||||
const { emailConfirmationTime } = S.Common;
|
||||
|
||||
if (!emailConfirmationTime) {
|
||||
S.Common.emailConfirmationTimeSet(U.Date.now());
|
||||
};
|
||||
|
||||
this.setState({ countdown: seconds });
|
||||
this.interval = window.setInterval(() => {
|
||||
let { countdown } = this.state;
|
||||
|
||||
countdown--;
|
||||
this.setState({ countdown });
|
||||
|
||||
if (!countdown) {
|
||||
S.Common.emailConfirmationTimeSet(0);
|
||||
window.clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
};
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export default EmailCollectionForm;
|
|
@ -24,6 +24,25 @@ export default {
|
|||
},
|
||||
}),
|
||||
|
||||
emailCollection: () => ({
|
||||
items: [
|
||||
{
|
||||
noButton: true
|
||||
},
|
||||
],
|
||||
param: {
|
||||
element: '#page.isFull #footer #button-help',
|
||||
classNameWrap: 'fixed',
|
||||
className: 'invertedColor',
|
||||
vertical: I.MenuDirection.Top,
|
||||
horizontal: I.MenuDirection.Right,
|
||||
noArrow: true,
|
||||
noClose: true,
|
||||
passThrough: true,
|
||||
offsetY: -4,
|
||||
},
|
||||
}),
|
||||
|
||||
objectCreationStart: () => ({
|
||||
category: translate('onboardingObjectCreationStart'),
|
||||
items: [
|
||||
|
|
|
@ -1998,11 +1998,13 @@ export const MembershipGetPortalLinkUrl = (callBack?: (message: any) => void) =>
|
|||
dispatcher.request(MembershipGetPortalLinkUrl.name, request, callBack);
|
||||
};
|
||||
|
||||
export const MembershipGetVerificationEmail = (email: string, isSubscribed: boolean, callBack?: (message: any) => void) => {
|
||||
export const MembershipGetVerificationEmail = (email: string, subscribeNews: boolean, subscribeTips: boolean, isOnboardingList: boolean, callBack?: (message: any) => void) => {
|
||||
const request = new Rpc.Membership.GetVerificationEmail.Request();
|
||||
|
||||
request.setEmail(email);
|
||||
request.setSubscribetonewsletter(isSubscribed);
|
||||
request.setSubscribetonewsletter(subscribeNews);
|
||||
request.setInsidertipsandtutorials(subscribeTips);
|
||||
request.setIsonboardinglist(isOnboardingList);
|
||||
|
||||
dispatcher.request(MembershipGetVerificationEmail.name, request, callBack);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue