1
0
Fork 0
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:
Mike Mhlv 2024-11-13 15:57:06 +00:00
parent 22c852fe3d
commit 9b06e13eac
14 changed files with 348 additions and 11 deletions

View file

@ -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 };

View file

@ -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/'
}
};
};

View file

@ -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 weve sent you to your email",
"emailCollectionStep2Title": "Youre 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",

View file

@ -30,4 +30,6 @@
@import "./share";
@import "./preview/common";
@import "./media/common";
@import "./media/common";
@import "./emailCollectionForm";

View 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; }
}
}

View file

@ -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; }

View file

@ -220,6 +220,10 @@ class App extends React.Component<object, State> {
componentDidMount () {
this.init();
window.setTimeout(() => {
Onboarding.start('emailCollection', false, true);
}, 1500);
};
init () {

View file

@ -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,
};

View file

@ -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 ? (

View file

@ -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) {

View file

@ -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) {

View 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;

View file

@ -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: [

View file

@ -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);
};