Temporary commit
This commit is contained in:
parent
a2f6e45e9c
commit
67e76a3f03
@ -55,12 +55,13 @@ class InputField {
|
|||||||
public container: HTMLElement;
|
public container: HTMLElement;
|
||||||
public input: HTMLElement;
|
public input: HTMLElement;
|
||||||
public inputFake: HTMLElement;
|
public inputFake: HTMLElement;
|
||||||
|
public label: HTMLLabelElement;
|
||||||
|
|
||||||
//public onLengthChange: (length: number, isOverflow: boolean) => void;
|
//public onLengthChange: (length: number, isOverflow: boolean) => void;
|
||||||
private wasInputFakeClientHeight: number;
|
protected wasInputFakeClientHeight: number;
|
||||||
private showScrollDebounced: () => void;
|
protected showScrollDebounced: () => void;
|
||||||
|
|
||||||
constructor(private options: {
|
constructor(protected options: {
|
||||||
placeholder?: string,
|
placeholder?: string,
|
||||||
label?: string,
|
label?: string,
|
||||||
name?: string,
|
name?: string,
|
||||||
@ -131,6 +132,10 @@ class InputField {
|
|||||||
input.addEventListener('input', () => checkAndSetRTL(input));
|
input.addEventListener('input', () => checkAndSetRTL(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(label) {
|
||||||
|
this.label = this.container.lastElementChild as HTMLLabelElement;
|
||||||
|
}
|
||||||
|
|
||||||
let processInput: () => void;
|
let processInput: () => void;
|
||||||
if(maxLength) {
|
if(maxLength) {
|
||||||
const labelEl = this.container.lastElementChild as HTMLLabelElement;
|
const labelEl = this.container.lastElementChild as HTMLLabelElement;
|
||||||
|
5
src/components/monkeys/index.ts
Normal file
5
src/components/monkeys/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import InputField from "../inputField";
|
||||||
|
|
||||||
|
export default interface Monkey {
|
||||||
|
attachToInputField: (inputField: InputField) => void;
|
||||||
|
}
|
59
src/components/monkeys/password.ts
Normal file
59
src/components/monkeys/password.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import lottieLoader, { RLottiePlayer } from "../../lib/lottieLoader";
|
||||||
|
import PasswordInputField from "../passwordInputField";
|
||||||
|
|
||||||
|
export default class PasswordMonkey {
|
||||||
|
public container: HTMLElement;
|
||||||
|
public animation: RLottiePlayer;
|
||||||
|
public needFrame = 0;
|
||||||
|
protected loadPromise: Promise<any>;
|
||||||
|
|
||||||
|
constructor(protected passwordInputField: PasswordInputField, protected size: number) {
|
||||||
|
this.container = document.createElement('div');
|
||||||
|
this.container.classList.add('media-sticker-wrapper');
|
||||||
|
}
|
||||||
|
|
||||||
|
public load() {
|
||||||
|
if(this.loadPromise) return this.loadPromise;
|
||||||
|
return this.loadPromise = lottieLoader.loadAnimationFromURL({
|
||||||
|
container: this.container,
|
||||||
|
loop: false,
|
||||||
|
autoplay: false,
|
||||||
|
width: this.size,
|
||||||
|
height: this.size,
|
||||||
|
noCache: true
|
||||||
|
//}, 'assets/img/TwoFactorSetupMonkeyClose.tgs').then(_animation => {
|
||||||
|
}, 'assets/img/TwoFactorSetupMonkeyPeek.tgs').then(_animation => {
|
||||||
|
//return;
|
||||||
|
this.animation = _animation;
|
||||||
|
this.animation.addListener('enterFrame', currentFrame => {
|
||||||
|
//console.log('enterFrame', currentFrame, this.needFrame);
|
||||||
|
|
||||||
|
if((this.animation.direction === 1 && currentFrame >= this.needFrame) ||
|
||||||
|
(this.animation.direction === -1 && currentFrame <= this.needFrame)) {
|
||||||
|
this.animation.setSpeed(1);
|
||||||
|
this.animation.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.passwordInputField.onVisibilityClickAdditional = () => {
|
||||||
|
if(this.passwordInputField.passwordVisible) {
|
||||||
|
this.animation.setDirection(1);
|
||||||
|
this.animation.curFrame = 0;
|
||||||
|
this.needFrame = 16;
|
||||||
|
this.animation.play();
|
||||||
|
} else {
|
||||||
|
this.animation.setDirection(-1);
|
||||||
|
this.animation.curFrame = 16;
|
||||||
|
this.needFrame = 0;
|
||||||
|
this.animation.play();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove() {
|
||||||
|
if(this.animation) {
|
||||||
|
this.animation.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
src/components/passwordInputField.ts
Normal file
41
src/components/passwordInputField.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { cancelEvent } from "../helpers/dom";
|
||||||
|
import InputField from "./inputField";
|
||||||
|
|
||||||
|
export default class PasswordInputField extends InputField {
|
||||||
|
public passwordVisible = false;
|
||||||
|
public toggleVisible: HTMLElement;
|
||||||
|
public onVisibilityClickAdditional: () => void;
|
||||||
|
|
||||||
|
constructor(options: {
|
||||||
|
label?: string,
|
||||||
|
name?: string
|
||||||
|
} = {}) {
|
||||||
|
super({
|
||||||
|
plainText: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
|
||||||
|
const input = this.input as HTMLInputElement;
|
||||||
|
input.type = 'password';
|
||||||
|
input.setAttribute('required', '');
|
||||||
|
input.autocomplete = 'off';
|
||||||
|
|
||||||
|
const toggleVisible = this.toggleVisible = document.createElement('span');
|
||||||
|
toggleVisible.classList.add('toggle-visible', 'tgico');
|
||||||
|
|
||||||
|
this.container.classList.add('input-field-password');
|
||||||
|
this.container.append(toggleVisible);
|
||||||
|
|
||||||
|
toggleVisible.addEventListener('click', this.onVisibilityClick);
|
||||||
|
toggleVisible.addEventListener('touchend', this.onVisibilityClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onVisibilityClick = (e: Event) => {
|
||||||
|
cancelEvent(e);
|
||||||
|
this.passwordVisible = !this.passwordVisible;
|
||||||
|
|
||||||
|
this.toggleVisible.classList.toggle('eye-hidden', this.passwordVisible);
|
||||||
|
(this.input as HTMLInputElement).type = this.passwordVisible ? 'text' : 'password';
|
||||||
|
this.onVisibilityClickAdditional && this.onVisibilityClickAdditional();
|
||||||
|
};
|
||||||
|
}
|
18
src/components/popups/confirmAction.ts
Normal file
18
src/components/popups/confirmAction.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import PopupElement, { addCancelButton, PopupButton, PopupOptions } from ".";
|
||||||
|
|
||||||
|
export default class PopupConfirmAction extends PopupElement {
|
||||||
|
constructor(className: string, buttons: PopupButton[], options: PopupOptions & Partial<{title: string, text: string}> = {}) {
|
||||||
|
super('popup-peer popup-confirm-action ' + className, addCancelButton(buttons), {
|
||||||
|
overlayClosable: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
|
||||||
|
this.title.innerHTML = options.title || 'Warning';
|
||||||
|
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.classList.add('popup-description');
|
||||||
|
p.innerHTML = options.text;
|
||||||
|
|
||||||
|
this.container.insertBefore(p, this.header.nextElementSibling);
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,23 @@
|
|||||||
import rootScope from "../../lib/rootScope";
|
import rootScope from "../../lib/rootScope";
|
||||||
import { blurActiveElement, cancelEvent, findUpClassName } from "../../helpers/dom";
|
import { blurActiveElement, findUpClassName } from "../../helpers/dom";
|
||||||
import { ripple } from "../ripple";
|
import { ripple } from "../ripple";
|
||||||
import animationIntersector from "../animationIntersector";
|
import animationIntersector from "../animationIntersector";
|
||||||
import appNavigationController, { NavigationItem } from "../appNavigationController";
|
import appNavigationController, { NavigationItem } from "../appNavigationController";
|
||||||
import { isMobileSafari, isSafari } from "../../helpers/userAgent";
|
|
||||||
|
|
||||||
export type PopupOptions = Partial<{closable: true, overlayClosable: true, withConfirm: string, body: true}>;
|
export type PopupButton = {
|
||||||
|
text: string,
|
||||||
|
callback?: () => void,
|
||||||
|
isDanger?: true,
|
||||||
|
isCancel?: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PopupOptions = Partial<{
|
||||||
|
closable: true,
|
||||||
|
overlayClosable: true,
|
||||||
|
withConfirm: string,
|
||||||
|
body: true
|
||||||
|
}>;
|
||||||
|
|
||||||
export default class PopupElement {
|
export default class PopupElement {
|
||||||
protected element = document.createElement('div');
|
protected element = document.createElement('div');
|
||||||
protected container = document.createElement('div');
|
protected container = document.createElement('div');
|
||||||
@ -140,9 +152,14 @@ export default class PopupElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PopupButton = {
|
export const addCancelButton = (buttons: PopupButton[]) => {
|
||||||
text: string,
|
const button = buttons.find(b => b.isCancel);
|
||||||
callback?: () => void,
|
if(!button) {
|
||||||
isDanger?: true,
|
buttons.push({
|
||||||
isCancel?: true
|
text: 'CANCEL',
|
||||||
|
isCancel: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttons;
|
||||||
};
|
};
|
||||||
|
@ -20,7 +20,7 @@ export default class Row {
|
|||||||
radioField: Row['radioField'],
|
radioField: Row['radioField'],
|
||||||
checkboxField: Row['checkboxField'],
|
checkboxField: Row['checkboxField'],
|
||||||
title: string,
|
title: string,
|
||||||
clickable: boolean,
|
clickable: boolean | ((e: Event) => void),
|
||||||
navigationTab: SliderSuperTab
|
navigationTab: SliderSuperTab
|
||||||
}> = {}) {
|
}> = {}) {
|
||||||
this.container = document.createElement('div');
|
this.container = document.createElement('div');
|
||||||
@ -63,14 +63,17 @@ export default class Row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(options.navigationTab) {
|
if(options.navigationTab) {
|
||||||
this.container.addEventListener('click', () => {
|
options.clickable = () => options.navigationTab.open();
|
||||||
if(this.freezed) return;
|
|
||||||
options.navigationTab.open();
|
|
||||||
});
|
|
||||||
options.clickable = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.clickable) {
|
if(options.clickable) {
|
||||||
|
if(typeof(options.clickable) === 'function') {
|
||||||
|
this.container.addEventListener('click', (e) => {
|
||||||
|
if(this.freezed) return;
|
||||||
|
(options.clickable as any)(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.container.classList.add('row-clickable', 'hover-effect');
|
this.container.classList.add('row-clickable', 'hover-effect');
|
||||||
ripple(this.container);
|
ripple(this.container);
|
||||||
}
|
}
|
||||||
|
135
src/components/sidebarLeft/tabs/2fa/enterPassword.ts
Normal file
135
src/components/sidebarLeft/tabs/2fa/enterPassword.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import AppTwoStepVerificationTab from ".";
|
||||||
|
import { SettingSection } from "../..";
|
||||||
|
import { attachClickEvent, cancelEvent } from "../../../../helpers/dom";
|
||||||
|
import { AccountPassword } from "../../../../layer";
|
||||||
|
import passwordManager from "../../../../lib/mtproto/passwordManager";
|
||||||
|
import Button from "../../../button";
|
||||||
|
import { putPreloader } from "../../../misc";
|
||||||
|
import PasswordMonkey from "../../../monkeys/password";
|
||||||
|
import PasswordInputField from "../../../passwordInputField";
|
||||||
|
import { ripple } from "../../../ripple";
|
||||||
|
import SidebarSlider, { SliderSuperTab } from "../../../slider";
|
||||||
|
import AppTwoStepVerificationReEnterPasswordTab from "./reEnterPassword";
|
||||||
|
|
||||||
|
export default class AppTwoStepVerificationEnterPasswordTab extends SliderSuperTab {
|
||||||
|
public state: AccountPassword;
|
||||||
|
|
||||||
|
constructor(slider: SidebarSlider) {
|
||||||
|
super(slider, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected init() {
|
||||||
|
this.container.classList.add('two-step-verification-enter-password');
|
||||||
|
this.title.innerHTML = this.state.pFlags.has_password ? 'Enter Your Password' : 'Enter a Password';
|
||||||
|
|
||||||
|
const section = new SettingSection({
|
||||||
|
noDelimiter: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputWrapper = document.createElement('div');
|
||||||
|
inputWrapper.classList.add('input-wrapper');
|
||||||
|
|
||||||
|
const passwordInputField = new PasswordInputField({
|
||||||
|
name: 'first-password',
|
||||||
|
label: this.state.pFlags.has_password ? this.state.hint ?? 'Password' : 'Enter a Password'
|
||||||
|
});
|
||||||
|
|
||||||
|
const monkey = new PasswordMonkey(passwordInputField, 157);
|
||||||
|
monkey.load();
|
||||||
|
|
||||||
|
const btnContinue = Button('btn-primary', {text: 'CONTINUE'});
|
||||||
|
|
||||||
|
inputWrapper.append(passwordInputField.container, btnContinue);
|
||||||
|
section.content.append(monkey.container, inputWrapper);
|
||||||
|
|
||||||
|
this.scrollable.container.append(section.container);
|
||||||
|
|
||||||
|
passwordInputField.input.addEventListener('keypress', (e) => {
|
||||||
|
if(passwordInputField.input.classList.contains('error')) {
|
||||||
|
passwordInputField.input.classList.remove('error');
|
||||||
|
btnContinue.innerText = 'CONTINUE';
|
||||||
|
ripple(btnContinue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.key === 'Enter') {
|
||||||
|
return btnContinue.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const verifyInput = () => {
|
||||||
|
if(!passwordInputField.value.length) {
|
||||||
|
passwordInputField.input.classList.add('error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if(this.state.pFlags.has_password) {
|
||||||
|
let getStateInterval: number;
|
||||||
|
|
||||||
|
let getState = () => {
|
||||||
|
// * just to check session relevance
|
||||||
|
if(!getStateInterval) {
|
||||||
|
getStateInterval = window.setInterval(getState, 10e3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return passwordManager.getState().then(_state => {
|
||||||
|
this.state = _state;
|
||||||
|
|
||||||
|
passwordInputField.label.innerText = this.state.hint ?? 'Password';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = (e?: Event) => {
|
||||||
|
if(!verifyInput()) {
|
||||||
|
cancelEvent(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btnContinue.setAttribute('disabled', 'true');
|
||||||
|
btnContinue.textContent = 'PLEASE WAIT...';
|
||||||
|
putPreloader(btnContinue);
|
||||||
|
|
||||||
|
const plainPassword = passwordInputField.value;
|
||||||
|
passwordManager.check(passwordInputField.value, this.state).then(auth => {
|
||||||
|
console.log(auth);
|
||||||
|
|
||||||
|
if(auth._ === 'auth.authorization') {
|
||||||
|
clearInterval(getStateInterval);
|
||||||
|
if(monkey) monkey.remove();
|
||||||
|
const tab = new AppTwoStepVerificationTab(this.slider);
|
||||||
|
tab.state = this.state;
|
||||||
|
tab.plainPassword = plainPassword;
|
||||||
|
tab.open();
|
||||||
|
}
|
||||||
|
}, (err) => {
|
||||||
|
btnContinue.removeAttribute('disabled');
|
||||||
|
passwordInputField.input.classList.add('error');
|
||||||
|
|
||||||
|
switch(err.type) {
|
||||||
|
default:
|
||||||
|
//btnContinue.innerText = err.type;
|
||||||
|
btnContinue.innerText = 'INVALID PASSWORD';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
getState();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
attachClickEvent(btnContinue, submit);
|
||||||
|
|
||||||
|
getState();
|
||||||
|
} else {
|
||||||
|
attachClickEvent(btnContinue, (e) => {
|
||||||
|
cancelEvent(e);
|
||||||
|
if(!verifyInput()) return;
|
||||||
|
|
||||||
|
const tab = new AppTwoStepVerificationReEnterPasswordTab(this.slider);
|
||||||
|
tab.state = this.state;
|
||||||
|
tab.open();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,20 @@
|
|||||||
import { generateSection, SettingSection } from "../..";
|
import { SettingSection } from "../..";
|
||||||
|
import { attachClickEvent } from "../../../../helpers/dom";
|
||||||
import { AccountPassword } from "../../../../layer";
|
import { AccountPassword } from "../../../../layer";
|
||||||
import appStickersManager from "../../../../lib/appManagers/appStickersManager";
|
import appStickersManager from "../../../../lib/appManagers/appStickersManager";
|
||||||
|
import passwordManager from "../../../../lib/mtproto/passwordManager";
|
||||||
import Button from "../../../button";
|
import Button from "../../../button";
|
||||||
|
import PopupConfirmAction from "../../../popups/confirmAction";
|
||||||
import SidebarSlider, { SliderSuperTab } from "../../../slider";
|
import SidebarSlider, { SliderSuperTab } from "../../../slider";
|
||||||
import { wrapSticker } from "../../../wrappers";
|
import { wrapSticker } from "../../../wrappers";
|
||||||
|
import AppTwoStepVerificationEnterPasswordTab from "./enterPassword";
|
||||||
|
|
||||||
export default class AppTwoStepVerificationTab extends SliderSuperTab {
|
export default class AppTwoStepVerificationTab extends SliderSuperTab {
|
||||||
public passwordState: AccountPassword;
|
public state: AccountPassword;
|
||||||
|
public plainPassword: string;
|
||||||
|
|
||||||
constructor(slider: SidebarSlider) {
|
constructor(slider: SidebarSlider) {
|
||||||
super(slider);
|
super(slider, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected init() {
|
protected init() {
|
||||||
@ -40,19 +45,40 @@ export default class AppTwoStepVerificationTab extends SliderSuperTab {
|
|||||||
section.content.append(stickerContainer);
|
section.content.append(stickerContainer);
|
||||||
|
|
||||||
const c = section.generateContentElement();
|
const c = section.generateContentElement();
|
||||||
if(this.passwordState.pFlags.has_password) {
|
if(this.state.pFlags.has_password) {
|
||||||
section.caption.innerHTML = 'You have enabled Two-Step verification.<br/>You\'ll need the password you set up here to log in to your Telegram account';
|
section.caption.innerHTML = 'You have enabled Two-Step verification.<br/>You\'ll need the password you set up here to log in to your Telegram account';
|
||||||
|
|
||||||
const btnChangePassword = Button('btn-primary btn-transparent', {icon: 'edit', text: 'Change Password'});
|
const btnChangePassword = Button('btn-primary btn-transparent', {icon: 'edit', text: 'Change Password'});
|
||||||
const btnRemovePassword = Button('btn-primary btn-transparent', {icon: 'passwordoff', text: 'Turn Password Off'});
|
const btnDisablePassword = Button('btn-primary btn-transparent', {icon: 'passwordoff', text: 'Turn Password Off'});
|
||||||
const btnSetRecoveryEmail = Button('btn-primary btn-transparent', {icon: 'email', text: 'Set Recovery Email'});
|
const btnSetRecoveryEmail = Button('btn-primary btn-transparent', {icon: 'email', text: 'Set Recovery Email'});
|
||||||
|
|
||||||
c.append(btnChangePassword, btnRemovePassword, btnSetRecoveryEmail);
|
attachClickEvent(btnDisablePassword, () => {
|
||||||
|
const popup = new PopupConfirmAction('popup-disable-password', [{
|
||||||
|
text: 'DISABLE',
|
||||||
|
callback: () => {
|
||||||
|
passwordManager.updateSettings({currentPassword: this.plainPassword});
|
||||||
|
},
|
||||||
|
isDanger: true,
|
||||||
|
}], {
|
||||||
|
title: 'Warning',
|
||||||
|
text: 'Are you sure you want to disable your password?'
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
c.append(btnChangePassword, btnDisablePassword, btnSetRecoveryEmail);
|
||||||
} else {
|
} else {
|
||||||
section.caption.innerHTML = 'You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS.';
|
section.caption.innerHTML = 'You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS.';
|
||||||
|
|
||||||
const btnSetPassword = Button('btn-primary', {text: 'SET PASSWORD'});
|
const btnSetPassword = Button('btn-primary', {text: 'SET PASSWORD'});
|
||||||
c.append(btnSetPassword);
|
c.append(btnSetPassword);
|
||||||
|
|
||||||
|
attachClickEvent(btnSetPassword, (e) => {
|
||||||
|
const tab = new AppTwoStepVerificationEnterPasswordTab(this.slider);
|
||||||
|
tab.state = this.state;
|
||||||
|
tab.open();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scrollable.container.append(section.container);
|
this.scrollable.container.append(section.container);
|
||||||
|
74
src/components/sidebarLeft/tabs/2fa/reEnterPassword.ts
Normal file
74
src/components/sidebarLeft/tabs/2fa/reEnterPassword.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import AppTwoStepVerificationTab from ".";
|
||||||
|
import { SettingSection } from "../..";
|
||||||
|
import { attachClickEvent, cancelEvent } from "../../../../helpers/dom";
|
||||||
|
import { AccountPassword } from "../../../../layer";
|
||||||
|
import passwordManager from "../../../../lib/mtproto/passwordManager";
|
||||||
|
import Button from "../../../button";
|
||||||
|
import { putPreloader } from "../../../misc";
|
||||||
|
import PasswordMonkey from "../../../monkeys/password";
|
||||||
|
import PasswordInputField from "../../../passwordInputField";
|
||||||
|
import { ripple } from "../../../ripple";
|
||||||
|
import SidebarSlider, { SliderSuperTab } from "../../../slider";
|
||||||
|
|
||||||
|
export default class AppTwoStepVerificationReEnterPasswordTab extends SliderSuperTab {
|
||||||
|
public state: AccountPassword;
|
||||||
|
|
||||||
|
constructor(slider: SidebarSlider) {
|
||||||
|
super(slider, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected init() {
|
||||||
|
this.container.classList.add('two-step-verification-enter-password');
|
||||||
|
this.title.innerHTML = 'Re-Enter your Password';
|
||||||
|
|
||||||
|
const section = new SettingSection({
|
||||||
|
noDelimiter: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputWrapper = document.createElement('div');
|
||||||
|
inputWrapper.classList.add('input-wrapper');
|
||||||
|
|
||||||
|
const passwordInputField = new PasswordInputField({
|
||||||
|
name: 're-enter-password',
|
||||||
|
label: 'Re-Enter your Password'
|
||||||
|
});
|
||||||
|
|
||||||
|
const monkey = new PasswordMonkey(passwordInputField, 157);
|
||||||
|
monkey.load();
|
||||||
|
|
||||||
|
const btnContinue = Button('btn-primary', {text: 'CONTINUE'});
|
||||||
|
|
||||||
|
inputWrapper.append(passwordInputField.container, btnContinue);
|
||||||
|
section.content.append(monkey.container, inputWrapper);
|
||||||
|
|
||||||
|
this.scrollable.container.append(section.container);
|
||||||
|
|
||||||
|
passwordInputField.input.addEventListener('keypress', (e) => {
|
||||||
|
if(passwordInputField.input.classList.contains('error')) {
|
||||||
|
passwordInputField.input.classList.remove('error');
|
||||||
|
btnContinue.innerText = 'CONTINUE';
|
||||||
|
ripple(btnContinue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.key === 'Enter') {
|
||||||
|
return btnContinue.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const verifyInput = () => {
|
||||||
|
if(!passwordInputField.value.length) {
|
||||||
|
passwordInputField.input.classList.add('error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
attachClickEvent(btnContinue, (e) => {
|
||||||
|
cancelEvent(e);
|
||||||
|
if(!verifyInput()) return;
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,12 @@
|
|||||||
import SidebarSlider, { SliderSuperTab } from "../../slider";
|
import SidebarSlider, { SliderSuperTab } from "../../slider";
|
||||||
import { generateSection, SettingSection } from "..";
|
import { generateSection, SettingSection } from "..";
|
||||||
import Row from "../../row";
|
import Row from "../../row";
|
||||||
import { InputPrivacyKey, PrivacyRule } from "../../../layer";
|
import { AccountPassword, InputPrivacyKey, PrivacyRule } from "../../../layer";
|
||||||
import appPrivacyManager from "../../../lib/appManagers/appPrivacyManager";
|
import appPrivacyManager from "../../../lib/appManagers/appPrivacyManager";
|
||||||
import AppPrivacyPhoneNumberTab from "./privacy/phoneNumber";
|
import AppPrivacyPhoneNumberTab from "./privacy/phoneNumber";
|
||||||
import AppTwoStepVerificationTab from "./2fa";
|
import AppTwoStepVerificationTab from "./2fa";
|
||||||
import passwordManager from "../../../lib/mtproto/passwordManager";
|
import passwordManager from "../../../lib/mtproto/passwordManager";
|
||||||
|
import AppTwoStepVerificationEnterPasswordTab from "./2fa/enterPassword";
|
||||||
|
|
||||||
export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
|
export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
|
||||||
constructor(slider: SidebarSlider) {
|
constructor(slider: SidebarSlider) {
|
||||||
@ -28,14 +29,25 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
|
|||||||
clickable: true
|
clickable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const tab = new AppTwoStepVerificationTab(this.slider);
|
let passwordState: AccountPassword;
|
||||||
|
const twoFactorRowOptions = {
|
||||||
const twoFactorRow = new Row({
|
|
||||||
icon: 'lock',
|
icon: 'lock',
|
||||||
title: 'Two-Step Verification',
|
title: 'Two-Step Verification',
|
||||||
subtitle: 'Loading...',
|
subtitle: 'Loading...',
|
||||||
navigationTab: tab
|
clickable: (e: Event) => {
|
||||||
});
|
let tab: AppTwoStepVerificationTab | AppTwoStepVerificationEnterPasswordTab;
|
||||||
|
if(passwordState.pFlags.has_password) {
|
||||||
|
tab = new AppTwoStepVerificationEnterPasswordTab(this.slider);
|
||||||
|
} else {
|
||||||
|
tab = new AppTwoStepVerificationTab(this.slider);
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.state = passwordState;
|
||||||
|
tab.open();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const twoFactorRow = new Row(twoFactorRowOptions);
|
||||||
twoFactorRow.freezed = true;
|
twoFactorRow.freezed = true;
|
||||||
|
|
||||||
const activeSessionRow = new Row({
|
const activeSessionRow = new Row({
|
||||||
@ -49,10 +61,11 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
|
|||||||
this.scrollable.append(section.container);
|
this.scrollable.append(section.container);
|
||||||
|
|
||||||
passwordManager.getState().then(state => {
|
passwordManager.getState().then(state => {
|
||||||
|
passwordState = state;
|
||||||
twoFactorRow.subtitle.innerText = state.pFlags.has_password ? 'On' : 'Off';
|
twoFactorRow.subtitle.innerText = state.pFlags.has_password ? 'On' : 'Off';
|
||||||
twoFactorRow.freezed = false;
|
twoFactorRow.freezed = false;
|
||||||
tab.passwordState = state;
|
|
||||||
//console.log('password state', state);
|
console.log('password state', state);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2465,7 +2465,7 @@ export class AppMessagesManager {
|
|||||||
if(document.type === 'video') {
|
if(document.type === 'video') {
|
||||||
messageText = '<i>Video' + (message.message ? ', ' : '') + '</i>';
|
messageText = '<i>Video' + (message.message ? ', ' : '') + '</i>';
|
||||||
} else if(document.type === 'voice') {
|
} else if(document.type === 'voice') {
|
||||||
messageText = '<i>Voice message</i>';
|
messageText = '<i>Voice message' + (message.message ? ', ' : '') + '</i>';
|
||||||
} else if(document.type === 'gif') {
|
} else if(document.type === 'gif') {
|
||||||
messageText = '<i>GIF' + (message.message ? ', ' : '') + '</i>';
|
messageText = '<i>GIF' + (message.message ? ', ' : '') + '</i>';
|
||||||
} else if(document.type === 'round') {
|
} else if(document.type === 'round') {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import type { AccountPassword, AccountPasswordInputSettings, AccountUpdatePasswordSettings, InputCheckPasswordSRP, PasswordKdfAlgo } from '../../layer';
|
||||||
|
import type CryptoWorkerMethods from '../crypto/crypto_methods';
|
||||||
import { MOUNT_CLASS_TO } from '../../config/debug';
|
import { MOUNT_CLASS_TO } from '../../config/debug';
|
||||||
import { AccountPassword } from '../../layer';
|
import appUsersManager from '../appManagers/appUsersManager';
|
||||||
import apiManager from './mtprotoworker';
|
import apiManager from './mtprotoworker';
|
||||||
//import { computeCheck } from "../crypto/srp";
|
//import { computeCheck } from "../crypto/srp";
|
||||||
|
|
||||||
@ -10,54 +12,71 @@ export class PasswordManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* public updateSettings(state: any, settings: any) {
|
public updateSettings(settings: {
|
||||||
var currentHashPromise;
|
hint?: string,
|
||||||
var newHashPromise;
|
email?: string,
|
||||||
var params: any = {
|
newPassword?: string,
|
||||||
new_settings: {
|
currentPassword?: string
|
||||||
_: 'account.passwordInputSettings',
|
} = {}) {
|
||||||
hint: settings.hint || ''
|
//state = Object.assign({}, state);
|
||||||
|
//state.new_algo = Object.assign({}, state.new_algo);
|
||||||
|
|
||||||
|
this.getState().then(state => {
|
||||||
|
let currentHashPromise: ReturnType<CryptoWorkerMethods['computeSRP']>;
|
||||||
|
let newHashPromise: Promise<Uint8Array>;
|
||||||
|
const params: AccountUpdatePasswordSettings = {
|
||||||
|
password: null,
|
||||||
|
new_settings: {
|
||||||
|
_: 'account.passwordInputSettings',
|
||||||
|
hint: settings.hint,
|
||||||
|
email: settings.email
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(settings.currentPassword) {
|
||||||
|
currentHashPromise = apiManager.computeSRP(settings.currentPassword, state);
|
||||||
|
} else {
|
||||||
|
currentHashPromise = Promise.resolve({
|
||||||
|
_: 'inputCheckPasswordEmpty'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
// * https://core.telegram.org/api/srp#setting-a-new-2fa-password, but still there is a mistake, TDesktop passes 'new_algo' everytime
|
||||||
if(typeof settings.cur_password === 'string' &&
|
const newAlgo = state.new_algo as PasswordKdfAlgo.passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow;
|
||||||
settings.cur_password.length > 0) {
|
const salt1 = new Uint8Array(newAlgo.salt1.length + 32);
|
||||||
currentHashPromise = this.makePasswordHash(state.current_salt, settings.cur_password);
|
salt1.randomize();
|
||||||
} else {
|
salt1.set(newAlgo.salt1, 0);
|
||||||
currentHashPromise = Promise.resolve([]);
|
newAlgo.salt1 = salt1;
|
||||||
}
|
|
||||||
|
if(settings.newPassword) {
|
||||||
if (typeof settings.new_password === 'string' &&
|
newHashPromise = Promise.resolve(new Uint8Array());
|
||||||
settings.new_password.length > 0) {
|
} else {
|
||||||
var saltRandom = new Array(8);
|
newHashPromise = Promise.resolve(new Uint8Array());
|
||||||
var newSalt = bufferConcat(state.new_salt, saltRandom);
|
|
||||||
secureRandom.nextBytes(saltRandom);
|
|
||||||
newHashPromise = this.makePasswordHash(newSalt, settings.new_password);
|
|
||||||
params.new_settings.new_salt = newSalt;
|
|
||||||
} else {
|
|
||||||
if(typeof settings.new_password === 'string') {
|
|
||||||
params.new_settings.new_salt = [];
|
|
||||||
}
|
}
|
||||||
newHashPromise = Promise.resolve([]);
|
|
||||||
}
|
return Promise.all([currentHashPromise, newHashPromise]).then((hashes) => {
|
||||||
|
params.password = hashes[0];
|
||||||
if(typeof settings.email === 'string') {
|
params.new_settings.new_algo = newAlgo;
|
||||||
params.new_settings.email = settings.email || '';
|
params.new_settings.new_password_hash = hashes[1];
|
||||||
}
|
|
||||||
|
return apiManager.invokeApi('account.updatePasswordSettings', params);
|
||||||
return Promise.all([currentHashPromise, newHashPromise]).then((hashes) => {
|
});
|
||||||
params.current_password_hash = hashes[0];
|
|
||||||
params.new_settings.new_password_hash = hashes[1];
|
|
||||||
|
|
||||||
return apiManager.invokeApi('account.updatePasswordSettings', params);
|
|
||||||
});
|
});
|
||||||
} */
|
}
|
||||||
|
|
||||||
public check(password: string, state: AccountPassword, options: any = {}) {
|
public check(password: string, state: AccountPassword, options: any = {}) {
|
||||||
return apiManager.computeSRP(password, state).then((inputCheckPassword) => {
|
return apiManager.computeSRP(password, state).then((inputCheckPassword) => {
|
||||||
|
//console.log('SRP', inputCheckPassword);
|
||||||
return apiManager.invokeApi('auth.checkPassword', {
|
return apiManager.invokeApi('auth.checkPassword', {
|
||||||
password: inputCheckPassword
|
password: inputCheckPassword
|
||||||
}, options);
|
}, options).then(auth => {
|
||||||
|
if(auth._ === 'auth.authorization') {
|
||||||
|
appUsersManager.saveApiUser(auth.user);
|
||||||
|
apiManager.setUserAuth(auth.user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,48 +1,28 @@
|
|||||||
//import CryptoWorker from '../lib/crypto/cryptoworker';
|
|
||||||
//import apiManager from '../lib/mtproto/apiManager';
|
|
||||||
import { putPreloader } from '../components/misc';
|
import { putPreloader } from '../components/misc';
|
||||||
import mediaSizes from '../helpers/mediaSizes';
|
import mediaSizes from '../helpers/mediaSizes';
|
||||||
import { isAppleMobile, isSafari } from '../helpers/userAgent';
|
|
||||||
import { AccountPassword } from '../layer';
|
import { AccountPassword } from '../layer';
|
||||||
import appStateManager from '../lib/appManagers/appStateManager';
|
import appStateManager from '../lib/appManagers/appStateManager';
|
||||||
import LottieLoader, { RLottiePlayer } from '../lib/lottieLoader';
|
|
||||||
//import passwordManager from '../lib/mtproto/passwordManager';
|
|
||||||
import apiManager from '../lib/mtproto/mtprotoworker';
|
import apiManager from '../lib/mtproto/mtprotoworker';
|
||||||
import passwordManager from '../lib/mtproto/passwordManager';
|
import passwordManager from '../lib/mtproto/passwordManager';
|
||||||
import { cancelEvent } from '../helpers/dom';
|
|
||||||
import Page from './page';
|
import Page from './page';
|
||||||
import pageIm from './pageIm';
|
import pageIm from './pageIm';
|
||||||
import InputField from '../components/inputField';
|
|
||||||
import Button from '../components/button';
|
import Button from '../components/button';
|
||||||
|
import PasswordInputField from '../components/passwordInputField';
|
||||||
|
import PasswordMonkey from '../components/monkeys/password';
|
||||||
|
import { ripple } from '../components/ripple';
|
||||||
|
|
||||||
const TEST = false;
|
const TEST = false;
|
||||||
let passwordInput: HTMLInputElement;
|
let passwordInput: HTMLInputElement;
|
||||||
|
|
||||||
let onFirstMount = (): Promise<any> => {
|
let onFirstMount = (): Promise<any> => {
|
||||||
let needFrame = 0;
|
|
||||||
let animation: RLottiePlayer;
|
|
||||||
|
|
||||||
let passwordVisible = false;
|
|
||||||
|
|
||||||
const btnNext = Button('btn-primary', {text: 'NEXT'});
|
const btnNext = Button('btn-primary', {text: 'NEXT'});
|
||||||
|
|
||||||
const passwordInputField = new InputField({
|
const passwordInputField = new PasswordInputField({
|
||||||
label: 'Password',
|
label: 'Password',
|
||||||
name: 'password',
|
name: 'password'
|
||||||
plainText: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
passwordInput = passwordInputField.input as HTMLInputElement;
|
passwordInput = passwordInputField.input as HTMLInputElement;
|
||||||
passwordInput.type = 'password';
|
|
||||||
passwordInput.setAttribute('required', '');
|
|
||||||
passwordInput.autocomplete = 'off';
|
|
||||||
|
|
||||||
const passwordInputLabel = passwordInput.nextElementSibling as HTMLLabelElement;
|
|
||||||
|
|
||||||
const toggleVisible = document.createElement('span');
|
|
||||||
toggleVisible.classList.add('toggle-visible', 'tgico');
|
|
||||||
|
|
||||||
passwordInputField.container.append(toggleVisible);
|
|
||||||
|
|
||||||
page.pageEl.querySelector('.input-wrapper').append(passwordInputField.container, btnNext);
|
page.pageEl.querySelector('.input-wrapper').append(passwordInputField.container, btnNext);
|
||||||
|
|
||||||
@ -57,13 +37,13 @@ let onFirstMount = (): Promise<any> => {
|
|||||||
return !TEST && passwordManager.getState().then(_state => {
|
return !TEST && passwordManager.getState().then(_state => {
|
||||||
state = _state;
|
state = _state;
|
||||||
|
|
||||||
passwordInputLabel.innerText = state.hint ?? 'Password';
|
passwordInputField.label.innerText = state.hint ?? 'Password';
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let handleError = (err: any) => {
|
let handleError = (err: any) => {
|
||||||
btnNext.removeAttribute('disabled');
|
btnNext.removeAttribute('disabled');
|
||||||
passwordInput.classList.add('error');
|
passwordInputField.input.classList.add('error');
|
||||||
|
|
||||||
switch(err.type) {
|
switch(err.type) {
|
||||||
default:
|
default:
|
||||||
@ -75,29 +55,6 @@ let onFirstMount = (): Promise<any> => {
|
|||||||
getState();
|
getState();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onVisibilityClick = function(this: typeof toggleVisible, e: Event) {
|
|
||||||
cancelEvent(e);
|
|
||||||
passwordVisible = !passwordVisible;
|
|
||||||
|
|
||||||
this.classList.toggle('eye-hidden', passwordVisible);
|
|
||||||
if(passwordVisible) {
|
|
||||||
passwordInput.setAttribute('type', 'text');
|
|
||||||
animation.setDirection(1);
|
|
||||||
animation.curFrame = 0;
|
|
||||||
needFrame = 16;
|
|
||||||
animation.play();
|
|
||||||
} else {
|
|
||||||
passwordInput.setAttribute('type', 'password');
|
|
||||||
animation.setDirection(-1);
|
|
||||||
animation.curFrame = 16;
|
|
||||||
needFrame = 0;
|
|
||||||
animation.play();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
toggleVisible.addEventListener('click', onVisibilityClick);
|
|
||||||
toggleVisible.addEventListener('touchend', onVisibilityClick);
|
|
||||||
|
|
||||||
let state: AccountPassword;
|
let state: AccountPassword;
|
||||||
|
|
||||||
btnNext.addEventListener('click', function(this, e) {
|
btnNext.addEventListener('click', function(this, e) {
|
||||||
@ -117,15 +74,14 @@ let onFirstMount = (): Promise<any> => {
|
|||||||
|
|
||||||
switch(response._) {
|
switch(response._) {
|
||||||
case 'auth.authorization':
|
case 'auth.authorization':
|
||||||
apiManager.setUserAuth(response.user.id);
|
|
||||||
|
|
||||||
clearInterval(getStateInterval);
|
clearInterval(getStateInterval);
|
||||||
pageIm.mount();
|
pageIm.mount();
|
||||||
if(animation) animation.remove();
|
if(monkey) monkey.remove();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
btnNext.removeAttribute('disabled');
|
btnNext.removeAttribute('disabled');
|
||||||
btnNext.innerText = response._;
|
btnNext.innerText = response._;
|
||||||
|
ripple(btnNext);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}).catch(handleError);
|
}).catch(handleError);
|
||||||
@ -134,42 +90,18 @@ let onFirstMount = (): Promise<any> => {
|
|||||||
passwordInput.addEventListener('keypress', function(this, e) {
|
passwordInput.addEventListener('keypress', function(this, e) {
|
||||||
this.classList.remove('error');
|
this.classList.remove('error');
|
||||||
btnNext.innerText = 'NEXT';
|
btnNext.innerText = 'NEXT';
|
||||||
|
ripple(btnNext);
|
||||||
|
|
||||||
if(e.key === 'Enter') {
|
if(e.key === 'Enter') {
|
||||||
return btnNext.click();
|
return btnNext.click();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* passwordInput.addEventListener('input', function(this, e) {
|
|
||||||
|
|
||||||
}); */
|
|
||||||
const size = mediaSizes.isMobile ? 100 : 166;
|
const size = mediaSizes.isMobile ? 100 : 166;
|
||||||
|
const monkey = new PasswordMonkey(passwordInputField, size);
|
||||||
|
page.pageEl.querySelector('.auth-image').append(monkey.container);
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
LottieLoader.loadAnimationFromURL({
|
monkey.load(),
|
||||||
container: page.pageEl.querySelector('.auth-image'),
|
|
||||||
loop: false,
|
|
||||||
autoplay: false,
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
noCache: true
|
|
||||||
//}, 'assets/img/TwoFactorSetupMonkeyClose.tgs').then(_animation => {
|
|
||||||
}, 'assets/img/TwoFactorSetupMonkeyPeek.tgs').then(_animation => {
|
|
||||||
//return;
|
|
||||||
animation = _animation;
|
|
||||||
animation.addListener('enterFrame', currentFrame => {
|
|
||||||
//console.log('enterFrame', e, needFrame);
|
|
||||||
|
|
||||||
if((animation.direction === 1 && currentFrame >= needFrame) ||
|
|
||||||
(animation.direction === -1 && currentFrame <= needFrame)) {
|
|
||||||
animation.setSpeed(1);
|
|
||||||
animation.pause();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
needFrame = 49;
|
|
||||||
//animation.play();
|
|
||||||
}),
|
|
||||||
|
|
||||||
getState()
|
getState()
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
transform-origin: left center;
|
transform-origin: left center;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
margin-top: calc((var(--height) - 1.5rem) / 2); // * Center of first line
|
margin-top: calc((var(--height) - 1.5rem) / 2); // * Center of first line
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
body.animation-level-0 & {
|
body.animation-level-0 & {
|
||||||
transition: none;
|
transition: none;
|
||||||
@ -301,4 +302,54 @@ input:focus, button:focus {
|
|||||||
margin-right: -1px;
|
margin-right: -1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-field-password {
|
||||||
|
.input-field-input {
|
||||||
|
padding-right: 2.5rem;
|
||||||
|
max-height: 54px;
|
||||||
|
|
||||||
|
&[type="password"] {
|
||||||
|
font-size: 2.25rem;
|
||||||
|
padding-left: calc(.875rem - var(--border-width));
|
||||||
|
|
||||||
|
@media (-webkit-min-device-pixel-ratio: 2) {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
letter-spacing: .125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.is-ios & {
|
||||||
|
// ! меньше 1rem будет зумить поле
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-visible {
|
||||||
|
position: absolute;
|
||||||
|
right: .375rem;
|
||||||
|
z-index: 2;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: $placeholder-color;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color .2s;
|
||||||
|
padding: .5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: $tgico-eye1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.eye-hidden:before {
|
||||||
|
content: $tgico-eye2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include hover() {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -843,6 +843,10 @@
|
|||||||
@include respond-to(not-handhelds) {
|
@include respond-to(not-handhelds) {
|
||||||
margin: 0 .625rem;
|
margin: 0 .625rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .btn-primary {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-name {
|
&-name {
|
||||||
@ -857,7 +861,7 @@
|
|||||||
padding: 0 .875rem;
|
padding: 0 .875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary, .checkbox-field, .radio-field {
|
.checkbox-field, .radio-field {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -905,6 +909,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.two-step-verification-enter-password {
|
||||||
|
.media-sticker-wrapper {
|
||||||
|
width: 157px;
|
||||||
|
height: 157px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.range-setting-selector {
|
.range-setting-selector {
|
||||||
padding: 1rem .875rem;
|
padding: 1rem .875rem;
|
||||||
|
|
||||||
|
@ -91,7 +91,8 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate3d(-50%, -50%, 0);
|
transform: translate3d(-50%, -50%, 0);
|
||||||
|
|
||||||
&-path {
|
// ! do not change it to &-path
|
||||||
|
.preloader-path {
|
||||||
stroke: $button-primary-background;
|
stroke: $button-primary-background;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,3 @@
|
|||||||
.page-password {
|
/* .page-password {
|
||||||
.input-field-input {
|
|
||||||
padding-right: 2.5rem;
|
|
||||||
max-height: 54px;
|
|
||||||
|
|
||||||
&[type="password"] {
|
|
||||||
font-size: 2.25rem;
|
|
||||||
padding-left: 10px;
|
|
||||||
|
|
||||||
@media (-webkit-min-device-pixel-ratio: 2) {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
letter-spacing: .125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.is-ios & {
|
|
||||||
// ! меньше 1rem будет зумить поле
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-visible {
|
} */
|
||||||
position: absolute;
|
|
||||||
right: .25rem;
|
|
||||||
z-index: 2;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
color: $placeholder-color;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: color .2s;
|
|
||||||
padding: .5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: $tgico-eye1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.eye-hidden:before {
|
|
||||||
content: $tgico-eye2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include hover() {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -4,25 +4,28 @@
|
|||||||
#{$parent} {
|
#{$parent} {
|
||||||
&-header {
|
&-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 0.4375rem;
|
margin-bottom: .4375rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.125rem 0.25rem;
|
padding: .125rem .25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-container {
|
&-container {
|
||||||
padding: 1rem 1.5rem 0.75rem 1rem;
|
padding: 1rem 1.5rem .75rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
padding-left: 0.75rem;
|
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 0.125rem;
|
margin-bottom: .125rem;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
padding-left: .75rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-description {
|
&-description {
|
||||||
padding: 0 0.25rem;
|
padding: 0 .25rem;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 1.625rem;
|
margin-bottom: 1.625rem;
|
||||||
min-width: 15rem;
|
min-width: 15rem;
|
||||||
@ -33,13 +36,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-buttons {
|
&-buttons {
|
||||||
margin-right: -0.75rem;
|
margin-right: -.75rem;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
& + .btn {
|
& + .btn {
|
||||||
margin-top: 0.625rem;
|
margin-top: .625rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user