Compare commits
6 commits
master
...
new-sync-d
Author | SHA1 | Date | |
---|---|---|---|
|
d8be27aa50 | ||
|
bdfb8d29f5 | ||
|
6b21df2c17 | ||
|
ded2992e44 | ||
|
9321a0ba37 | ||
|
7ab5a0c978 |
171 changed files with 1748 additions and 6463 deletions
|
@ -1,91 +0,0 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const packageJSON = require('../package.json');
|
||||
const fetch = require('node-fetch');
|
||||
const decompress = require('decompress');
|
||||
const os = require('os');
|
||||
const del = require('del');
|
||||
|
||||
const downloadLBRYFirst = targetPlatform =>
|
||||
new Promise((resolve, reject) => {
|
||||
const lbryFirstURLTemplate = packageJSON.lbrySettings.LBRYFirstUrlTemplate;
|
||||
const lbryFirstVersion = packageJSON.lbrySettings.LBRYFirstVersion;
|
||||
const lbryFirstDir = path.join(__dirname, '..', packageJSON.lbrySettings.LBRYFirstDir);
|
||||
let lbryFirstFileName = packageJSON.lbrySettings.LBRYFirstFileName;
|
||||
|
||||
const currentPlatform = os.platform();
|
||||
|
||||
let lbryFirstPlatform = process.env.TARGET || targetPlatform || currentPlatform;
|
||||
if (lbryFirstPlatform === 'linux') lbryFirstPlatform = 'Linux';
|
||||
if (lbryFirstPlatform === 'mac' || lbryFirstPlatform === 'darwin') lbryFirstPlatform = 'Darwin';
|
||||
if (lbryFirstPlatform === 'win32' || lbryFirstPlatform === 'windows') {
|
||||
lbryFirstPlatform = 'Windows';
|
||||
lbryFirstFileName += '.exe';
|
||||
}
|
||||
const lbryFirstFilePath = path.join(lbryFirstDir, lbryFirstFileName);
|
||||
const lbryFirstVersionPath = path.join(__dirname, 'lbryFirst.ver');
|
||||
const tmpZipPath = path.join(__dirname, '..', 'dist', 'lbryFirst.zip');
|
||||
const lbryFirstURL = lbryFirstURLTemplate.replace(/LBRYFIRSTVER/g, lbryFirstVersion).replace(/OSNAME/g, lbryFirstPlatform);
|
||||
console.log('URL:', lbryFirstURL);
|
||||
|
||||
// If a lbryFirst and lbryFirst.ver exists, check to see if it matches the current lbryFirst version
|
||||
const hasLbryFirstDownloaded = fs.existsSync(lbryFirstFilePath);
|
||||
const hasLbryFirstVersion = fs.existsSync(lbryFirstVersionPath);
|
||||
let downloadedLbryFirstVersion;
|
||||
|
||||
if (hasLbryFirstVersion) {
|
||||
downloadedLbryFirstVersion = fs.readFileSync(lbryFirstVersionPath, 'utf8');
|
||||
}
|
||||
|
||||
if (hasLbryFirstDownloaded && hasLbryFirstVersion && downloadedLbryFirstVersion === lbryFirstVersion) {
|
||||
console.log('\x1b[34minfo\x1b[0m LbryFirst already downloaded');
|
||||
resolve('Done');
|
||||
} else {
|
||||
console.log('\x1b[34minfo\x1b[0m Downloading lbryFirst...');
|
||||
fetch(lbryFirstURL, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/zip',
|
||||
},
|
||||
})
|
||||
.then(response => response.buffer())
|
||||
.then(
|
||||
result =>
|
||||
new Promise((newResolve, newReject) => {
|
||||
const distPath = path.join(__dirname, '..', 'dist');
|
||||
const hasDistFolder = fs.existsSync(distPath);
|
||||
|
||||
if (!hasDistFolder) {
|
||||
fs.mkdirSync(distPath);
|
||||
}
|
||||
|
||||
fs.writeFile(tmpZipPath, result, error => {
|
||||
if (error) return newReject(error);
|
||||
return newResolve();
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(() => del(`${lbryFirstFilePath}*`))
|
||||
.then()
|
||||
.then(() =>
|
||||
decompress(tmpZipPath, lbryFirstDir, {
|
||||
filter: file => path.basename(file.path) === lbryFirstFileName,
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
console.log('\x1b[32msuccess\x1b[0m LbryFirst downloaded!');
|
||||
if (hasLbryFirstVersion) {
|
||||
del(lbryFirstVersionPath);
|
||||
}
|
||||
|
||||
fs.writeFileSync(lbryFirstVersionPath, lbryFirstVersion, 'utf8');
|
||||
resolve('Done');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`\x1b[31merror\x1b[0m LbryFirst download failed due to: \x1b[35m${error}\x1b[0m`);
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
downloadLBRYFirst();
|
|
@ -13,6 +13,7 @@ const config = {
|
|||
LBRY_API_URL: process.env.LBRY_API_URL, //api.lbry.com',
|
||||
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //player.odysee.com
|
||||
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
|
||||
LBRYSYNC_API: process.env.LBRYSYNC_API,
|
||||
SEARCH_SERVER_API: process.env.SEARCH_SERVER_API,
|
||||
CLOUD_CONNECT_SITE_NAME: process.env.CLOUD_CONNECT_SITE_NAME,
|
||||
COMMENT_SERVER_API: process.env.COMMENT_SERVER_API,
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import path from 'path';
|
||||
import { spawn, execSync } from 'child_process';
|
||||
|
||||
export default class LbryFirstInstance {
|
||||
static lbryFirstPath =
|
||||
process.env.LBRY_FIRST_DAEMON ||
|
||||
(process.env.NODE_ENV === 'production'
|
||||
? path.join(process.resourcesPath, 'static/lbry-first', 'lbry-first')
|
||||
: path.join(__static, 'lbry-first/lbry-first'));
|
||||
|
||||
static headersPath =
|
||||
process.env.LBRY_FIRST_DAEMON ||
|
||||
(process.env.NODE_ENV === 'production'
|
||||
? path.join(process.resourcesPath, 'static/lbry-first', 'headers')
|
||||
: path.join(__static, 'lbry-first/headers'));
|
||||
|
||||
subprocess;
|
||||
handlers;
|
||||
|
||||
constructor() {
|
||||
this.handlers = [];
|
||||
}
|
||||
|
||||
launch() {
|
||||
let flags = ['serve'];
|
||||
console.log(`LbryFirst: ${LbryFirstInstance.lbryFirstPath}`);
|
||||
this.subprocess = spawn(LbryFirstInstance.lbryFirstPath, flags);
|
||||
this.subprocess.stdout.on('data', data => console.log(`LbryFirst: ${data}`));
|
||||
this.subprocess.stderr.on('data', data => console.error(`LbryFirst: ${data}`));
|
||||
this.subprocess.on('exit', () => this.fire('exit'));
|
||||
this.subprocess.on('error', error => console.error(`LbryFirst error: ${error}`));
|
||||
}
|
||||
|
||||
quit() {
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
execSync(`taskkill /pid ${this.subprocess.pid} /t /f`);
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
} else {
|
||||
this.subprocess.kill();
|
||||
}
|
||||
}
|
||||
|
||||
// Follows the publish/subscribe pattern
|
||||
|
||||
// Subscribe method
|
||||
on(event, handler, context = handler) {
|
||||
this.handlers.push({ event, handler: handler.bind(context) });
|
||||
}
|
||||
|
||||
// Publish method
|
||||
fire(event, args) {
|
||||
this.handlers.forEach(topic => {
|
||||
if (topic.event === event) topic.handler(args);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ import https from 'https';
|
|||
import { app, dialog, ipcMain, session, shell, BrowserWindow } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import Lbry from 'lbry';
|
||||
import LbryFirstInstance from './LbryFirstInstance';
|
||||
import Daemon from './Daemon';
|
||||
import isDev from 'electron-is-dev';
|
||||
import createTray from './createTray';
|
||||
|
@ -18,6 +17,7 @@ import installDevtools from './installDevtools';
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { diskSpaceLinux, diskSpaceWindows, diskSpaceMac } from '../ui/util/diskspace';
|
||||
import { generateSalt, generateSaltSeed, deriveSecrets, walletHmac } from './sync/sync.js';
|
||||
|
||||
const { download } = require('electron-dl');
|
||||
const mime = require('mime');
|
||||
|
@ -58,7 +58,6 @@ let rendererWindow;
|
|||
|
||||
let tray; // eslint-disable-line
|
||||
let daemon;
|
||||
let lbryFirst;
|
||||
|
||||
const appState = {};
|
||||
const PROTOCOL = 'lbry';
|
||||
|
@ -115,51 +114,6 @@ const startDaemon = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
let isLbryFirstRunning = false;
|
||||
const startLbryFirst = async () => {
|
||||
if (isLbryFirstRunning) {
|
||||
console.log('LbryFirst already running');
|
||||
handleLbryFirstLaunched();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('LbryFirst: Starting...');
|
||||
|
||||
try {
|
||||
lbryFirst = new LbryFirstInstance();
|
||||
lbryFirst.on('exit', e => {
|
||||
if (!isDev) {
|
||||
lbryFirst = null;
|
||||
isLbryFirstRunning = false;
|
||||
if (!appState.isQuitting) {
|
||||
dialog.showErrorBox(
|
||||
'LbryFirst has Exited',
|
||||
'The lbryFirst may have encountered an unexpected error, or another lbryFirst instance is already running. \n\n',
|
||||
e
|
||||
);
|
||||
}
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('LbryFirst: Failed to create new instance\n\n', e);
|
||||
}
|
||||
|
||||
console.log('LbryFirst: Running...');
|
||||
|
||||
try {
|
||||
await lbryFirst.launch();
|
||||
handleLbryFirstLaunched();
|
||||
} catch (e) {
|
||||
isLbryFirstRunning = false;
|
||||
console.log('LbryFirst: Failed to start\n', e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLbryFirstLaunched = () => {
|
||||
isLbryFirstRunning = true;
|
||||
rendererWindow.webContents.send('lbry-first-launched');
|
||||
};
|
||||
|
||||
// When we are starting the app, ensure there are no other apps already running
|
||||
const gotSingleInstanceLock = app.requestSingleInstanceLock();
|
||||
|
@ -271,10 +225,6 @@ app.on('will-quit', event => {
|
|||
daemon.quit();
|
||||
event.preventDefault();
|
||||
}
|
||||
if (lbryFirst) {
|
||||
lbryFirst.quit();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (rendererWindow) {
|
||||
tray.destroy();
|
||||
|
@ -377,6 +327,42 @@ ipcMain.on('get-disk-space', async (event) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Sync cryptography
|
||||
ipcMain.on('get-salt-seed', () => {
|
||||
const saltSeed = generateSaltSeed();
|
||||
rendererWindow.webContents.send('got-salt-seed', saltSeed);
|
||||
});
|
||||
|
||||
ipcMain.on('get-secrets', (event, password, email, saltseed) => {
|
||||
console.log('password, salt', password, email, saltseed);
|
||||
const callback = (result) => {
|
||||
console.log('callback result', result);
|
||||
rendererWindow.webContents.send('got-secrets', result);
|
||||
};
|
||||
deriveSecrets(password, email, saltseed, callback);
|
||||
});
|
||||
|
||||
ipcMain.handle('invoke-get-secrets', (event, password, email, saltseed) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (err, result) => {
|
||||
console.log('callback result', result);
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
return resolve(result);
|
||||
};
|
||||
console.log('password, salt', password, email, saltseed);
|
||||
deriveSecrets(password, email, saltseed, callback);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('invoke-get-salt-seed', (event) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const saltSeed = generateSaltSeed();
|
||||
return resolve(saltSeed);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on('version-info-requested', () => {
|
||||
function formatRc(ver) {
|
||||
// Adds dash if needed to make RC suffix SemVer friendly
|
||||
|
@ -453,15 +439,6 @@ ipcMain.on('version-info-requested', () => {
|
|||
requestLatestRelease();
|
||||
});
|
||||
|
||||
ipcMain.on('launch-lbry-first', async () => {
|
||||
try {
|
||||
await startLbryFirst();
|
||||
} catch (e) {
|
||||
console.log('Failed to start LbryFirst');
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
process.on('uncaughtException', error => {
|
||||
console.log(error);
|
||||
dialog.showErrorBox('Error Encountered', `Caught error: ${error}`);
|
||||
|
@ -628,3 +605,5 @@ ipcMain.on('upgrade', (event, installerPath) => {
|
|||
});
|
||||
app.quit();
|
||||
});
|
||||
|
||||
|
||||
|
|
12
electron/sync/package.json
Normal file
12
electron/sync/package.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "test",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "testsync.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "module"
|
||||
}
|
87
electron/sync/sync.js
Normal file
87
electron/sync/sync.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
import crypto from 'crypto';
|
||||
|
||||
export function generateSalt(email, seed) {
|
||||
const hashInput = (email + ':' + seed).toString('utf8');
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(hashInput);
|
||||
const hash_output = hash.digest('hex').toString('utf8');
|
||||
return hash_output;
|
||||
}
|
||||
|
||||
export function generateSaltSeed() {
|
||||
return crypto.randomBytes(32).toString('hex');
|
||||
}
|
||||
|
||||
export function createHmac(serverWalletState, hmacKey) {
|
||||
|
||||
return crypto.randomBytes(32).toString('hex');
|
||||
}
|
||||
|
||||
export function checkHmac(serverWalletState, hmacKey, hmac) {
|
||||
return crypto.randomBytes(32).toString('hex');
|
||||
}
|
||||
|
||||
export function deriveSecrets(rootPassword, email, saltSeed, callback) {
|
||||
const encodedPassword = Buffer.from(rootPassword.normalize('NFKC'));
|
||||
const encodedEmail = Buffer.from(email);
|
||||
const SCRYPT_N = 1 << 20;
|
||||
const SCRYPT_R = 8;
|
||||
const SCRYPT_P = 1;
|
||||
const KEY_LENGTH = 32;
|
||||
const NUM_KEYS = 3;
|
||||
const MAXMEM_MULTIPLIER = 256;
|
||||
const DEFAULT_MAXMEM = MAXMEM_MULTIPLIER * SCRYPT_N * SCRYPT_R;
|
||||
|
||||
function getKeyParts(key) {
|
||||
const providerKey = key.slice(0, KEY_LENGTH).toString('base64');
|
||||
const hmacKey = key.slice(KEY_LENGTH, KEY_LENGTH * 2).toString('base64');
|
||||
const dataKey = key.slice(KEY_LENGTH * 2).toString('base64');
|
||||
return { providerKey, hmacKey, dataKey }; // Buffer aa bb cc 6c
|
||||
}
|
||||
|
||||
const salt = generateSalt(encodedEmail, saltSeed);
|
||||
|
||||
const scryptCallback = (err, key) => {
|
||||
if (err) {
|
||||
// handle error
|
||||
}
|
||||
|
||||
callback(err, getKeyParts(key));
|
||||
};
|
||||
|
||||
crypto.scrypt(encodedPassword, salt, KEY_LENGTH * NUM_KEYS, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P, maxmem: DEFAULT_MAXMEM }, scryptCallback);
|
||||
}
|
||||
|
||||
export function walletHmac(inputString, hmacKey) {
|
||||
const res = crypto.createHmac('sha256', hmacKey)
|
||||
.update(inputString.toString('utf8'))
|
||||
.digest('hex');
|
||||
console.log('hmac res', res)
|
||||
return res;
|
||||
}
|
||||
|
||||
export function aesEncrypt(text, key) {
|
||||
try {
|
||||
const iv = crypto.randomBytes(16);
|
||||
let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
|
||||
let encrypted = cipher.update(text);
|
||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||
return { iv: iv.toString('hex'), encryptedData: encrypted.toString('hex') };
|
||||
} catch (e) {
|
||||
return { error: `Wallet decrypt failed error: ${e.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
export function aesDecrypt(cipher, key) {
|
||||
try {
|
||||
let iv = Buffer.from(cipher.iv, 'hex');
|
||||
let encryptedText = Buffer.from(cipher.encryptedData, 'hex');
|
||||
let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
|
||||
let decrypted = decipher.update(encryptedText);
|
||||
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||
// handle errors here
|
||||
return { result: decrypted.toString() };
|
||||
} catch (e) {
|
||||
return { error: `Wallet decrypt failed error: ${e.message}`};
|
||||
}
|
||||
}
|
70
electron/sync/testsync.js
Normal file
70
electron/sync/testsync.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import test from 'tape';
|
||||
// import sync from '../sync.js';
|
||||
import { generateSalt, generateSaltSeed, deriveSecrets, walletHmac, aesEncrypt, aesDecrypt } from './sync.js';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
export default function doTest() {
|
||||
test('Generate sync seed', (assert) => {
|
||||
const seed = generateSaltSeed();
|
||||
console.log('seed', seed);
|
||||
assert.pass(seed.length === 64);
|
||||
assert.end();
|
||||
});
|
||||
|
||||
test('Generate salt', (assert) => {
|
||||
const seed = '80b3aff252588c4097df8f79ad42c8a5cf8d7c9e5339efec8bdb8bc7c5cf25ca';
|
||||
const email = 'example@example.com';
|
||||
const salt = generateSalt(email, seed);
|
||||
console.log('salt', salt);
|
||||
const expected = 'be6074a96f3ce812ea51b95d48e4b10493e14f6a329df7c0816018c6239661fe';
|
||||
const actual = salt;
|
||||
|
||||
assert.equal(actual, expected,
|
||||
'salt is expected value.');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
test('Derive Keys', (assert) => {
|
||||
const seed = '80b3aff252588c4097df8f79ad42c8a5cf8d7c9e5339efec8bdb8bc7c5cf25ca';
|
||||
const email = 'example@example.com';
|
||||
|
||||
const expectedHmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8='; //base64
|
||||
const expectedProviderKey = 'HKo/J+x4Hsy2NkMvj2JB9RI0yrvEiB4QSA/NHPaT/cA=';
|
||||
// add expectedDataKey to test
|
||||
|
||||
function cb(e, r) {
|
||||
console.log('derive keys result:', r);
|
||||
assert.equal(r.hmacKey, expectedHmacKey, 'hmac is expected value');
|
||||
assert.equal(r.providerKey, expectedProviderKey, 'lbryid password is expected value');
|
||||
assert.end();
|
||||
}
|
||||
|
||||
deriveSecrets('pass', email, seed, cb);
|
||||
});
|
||||
|
||||
test('CreateHmac', (assert) => {
|
||||
const hmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8=';
|
||||
const sequence = 1;
|
||||
const walletState = `zo4MTkyOjE2OjE68QlIU76+W91/v/F1tu8h+kGB0Ee`;
|
||||
const expectedHmacHex = '9fe70ebdeaf85b3afe5ae42e52f946acc54ded0350acacdded821845217839d4';
|
||||
|
||||
const input_str = `${sequence}:${walletState}`;
|
||||
const hmacHex = walletHmac(input_str, hmacKey);
|
||||
assert.equal(hmacHex, expectedHmacHex);
|
||||
assert.end();
|
||||
});
|
||||
|
||||
test('Encrypt/Decrypt', (assert) => {
|
||||
const key = crypto.randomBytes(32);
|
||||
// const wrongKey = crypto.randomBytes(32); // todo: what tests should fail; how much error handling needed?
|
||||
// test a handy json file
|
||||
const input = fs.readFileSync('../../package.json', 'utf8');
|
||||
const cipher = aesEncrypt(input, key);
|
||||
console.log('cipher', cipher);
|
||||
const output = aesDecrypt(cipher, key);
|
||||
assert.equal(input, output.result);
|
||||
assert.end();
|
||||
});
|
||||
}
|
||||
|
||||
doTest();
|
|
@ -1,184 +0,0 @@
|
|||
// @flow
|
||||
/*
|
||||
LBRY FIRST does not work due to api changes
|
||||
*/
|
||||
import 'proxy-polyfill';
|
||||
|
||||
const CHECK_LBRYFIRST_STARTED_TRY_NUMBER = 200;
|
||||
//
|
||||
// Basic LBRYFIRST connection config
|
||||
// Offers a proxy to call LBRYFIRST methods
|
||||
|
||||
//
|
||||
const LbryFirst: LbryFirstTypes = {
|
||||
isConnected: false,
|
||||
connectPromise: null,
|
||||
lbryFirstConnectionString: 'http://localhost:1337/rpc',
|
||||
apiRequestHeaders: { 'Content-Type': 'application/json' },
|
||||
|
||||
// Allow overriding lbryFirst connection string (e.g. to `/api/proxy` for lbryweb)
|
||||
setLbryFirstConnectionString: (value: string) => {
|
||||
LbryFirst.lbryFirstConnectionString = value;
|
||||
},
|
||||
|
||||
setApiHeader: (key: string, value: string) => {
|
||||
LbryFirst.apiRequestHeaders = Object.assign(LbryFirst.apiRequestHeaders, { [key]: value });
|
||||
},
|
||||
|
||||
unsetApiHeader: key => {
|
||||
Object.keys(LbryFirst.apiRequestHeaders).includes(key) &&
|
||||
delete LbryFirst.apiRequestHeaders['key'];
|
||||
},
|
||||
// Allow overriding Lbry methods
|
||||
overrides: {},
|
||||
setOverride: (methodName, newMethod) => {
|
||||
LbryFirst.overrides[methodName] = newMethod;
|
||||
},
|
||||
getApiRequestHeaders: () => LbryFirst.apiRequestHeaders,
|
||||
|
||||
// LbryFirst Methods
|
||||
status: (params = {}) => lbryFirstCallWithResult('status', params),
|
||||
stop: () => lbryFirstCallWithResult('stop', {}),
|
||||
version: () => lbryFirstCallWithResult('version', {}),
|
||||
|
||||
// Upload to youtube
|
||||
upload: (params: { title: string, description: string, file_path: ?string } = {}) => {
|
||||
// Only upload when originally publishing for now
|
||||
if (!params.file_path) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const uploadParams: {
|
||||
Title: string,
|
||||
Description: string,
|
||||
FilePath: string,
|
||||
Category: string,
|
||||
Keywords: string,
|
||||
} = {
|
||||
Title: params.title,
|
||||
Description: params.description,
|
||||
FilePath: params.file_path,
|
||||
Category: '',
|
||||
Keywords: '',
|
||||
};
|
||||
|
||||
return lbryFirstCallWithResult('youtube.Upload', uploadParams);
|
||||
},
|
||||
|
||||
hasYTAuth: (token: string) => {
|
||||
const hasYTAuthParams = {};
|
||||
hasYTAuthParams.AuthToken = token;
|
||||
return lbryFirstCallWithResult('youtube.HasAuth', hasYTAuthParams);
|
||||
},
|
||||
|
||||
ytSignup: () => {
|
||||
const emptyParams = {};
|
||||
return lbryFirstCallWithResult('youtube.Signup', emptyParams);
|
||||
},
|
||||
|
||||
remove: () => {
|
||||
const emptyParams = {};
|
||||
return lbryFirstCallWithResult('youtube.Remove', emptyParams);
|
||||
},
|
||||
|
||||
// Connect to lbry-first
|
||||
connect: () => {
|
||||
if (LbryFirst.connectPromise === null) {
|
||||
LbryFirst.connectPromise = new Promise((resolve, reject) => {
|
||||
let tryNum = 0;
|
||||
// Check every half second to see if the lbryFirst is accepting connections
|
||||
function checkLbryFirstStarted() {
|
||||
tryNum += 1;
|
||||
LbryFirst.status()
|
||||
.then(resolve)
|
||||
.catch(() => {
|
||||
if (tryNum <= CHECK_LBRYFIRST_STARTED_TRY_NUMBER) {
|
||||
setTimeout(checkLbryFirstStarted, tryNum < 50 ? 400 : 1000);
|
||||
} else {
|
||||
reject(new Error('Unable to connect to LBRY'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkLbryFirstStarted();
|
||||
});
|
||||
}
|
||||
|
||||
// Flow thinks this could be empty, but it will always return a promise
|
||||
// $FlowFixMe
|
||||
return LbryFirst.connectPromise;
|
||||
},
|
||||
};
|
||||
|
||||
function checkAndParse(response) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response.json();
|
||||
}
|
||||
return response.json().then(json => {
|
||||
let error;
|
||||
if (json.error) {
|
||||
const errorMessage = typeof json.error === 'object' ? json.error.message : json.error;
|
||||
error = new Error(errorMessage);
|
||||
} else {
|
||||
error = new Error('Protocol error with unknown response signature');
|
||||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
export function apiCall(method: string, params: ?{}, resolve: Function, reject: Function) {
|
||||
const counter = new Date().getTime();
|
||||
const paramsArray = [params];
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: LbryFirst.apiRequestHeaders,
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method,
|
||||
params: paramsArray,
|
||||
id: counter,
|
||||
}),
|
||||
};
|
||||
|
||||
return fetch(LbryFirst.lbryFirstConnectionString, options)
|
||||
.then(checkAndParse)
|
||||
.then(response => {
|
||||
const error = response.error || (response.result && response.result.error);
|
||||
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
return resolve(response.result);
|
||||
})
|
||||
.catch(reject);
|
||||
}
|
||||
|
||||
function lbryFirstCallWithResult(name: string, params: ?{} = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiCall(
|
||||
name,
|
||||
params,
|
||||
result => {
|
||||
resolve(result);
|
||||
},
|
||||
reject
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// This is only for a fallback
|
||||
// If there is a LbryFirst method that is being called by an app, it should be added to /flow-typed/LbryFirst.js
|
||||
const lbryFirstProxy = new Proxy(LbryFirst, {
|
||||
get(target: LbryFirstTypes, name: string) {
|
||||
if (name in target) {
|
||||
return target[name];
|
||||
}
|
||||
|
||||
return (params = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
apiCall(name, params, resolve, reject);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default lbryFirstProxy;
|
|
@ -1,3 +0,0 @@
|
|||
import Recsys from './recsys';
|
||||
|
||||
export default Recsys;
|
|
@ -1,257 +0,0 @@
|
|||
import { selectUser } from 'redux/selectors/user';
|
||||
import { makeSelectRecommendedRecsysIdForClaimId } from 'redux/selectors/search';
|
||||
import { v4 as Uuidv4 } from 'uuid';
|
||||
import { parseURI } from 'util/lbryURI';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { selectPlayingUri, selectPrimaryUri } from 'redux/selectors/content';
|
||||
import { makeSelectClientSetting, selectDaemonSettings } from 'redux/selectors/settings';
|
||||
import { history } from 'ui/store';
|
||||
|
||||
const recsysEndpoint = 'https://clickstream.odysee.com/log/video/view';
|
||||
const recsysId = 'lighthouse-v0';
|
||||
|
||||
const getClaimIdsFromUris = (uris) => {
|
||||
return uris
|
||||
? uris.map((uri) => {
|
||||
try {
|
||||
const { claimId } = parseURI(uri);
|
||||
return claimId;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
: [];
|
||||
};
|
||||
|
||||
const recsys = {
|
||||
entries: {},
|
||||
debug: false,
|
||||
/**
|
||||
* Provides for creating, updating, and sending Clickstream data object Entries.
|
||||
* Entries are Created either when recommendedContent loads, or when recommendedContent is clicked.
|
||||
* If recommended content is clicked, An Entry with parentUuid is created.
|
||||
* On page load, find an empty entry with your claimId, or create a new entry and record to it.
|
||||
* The entry will be populated with the following:
|
||||
* - parentUuid // optional
|
||||
* - Uuid
|
||||
* - claimId
|
||||
* - recommendedClaims [] // optionally empty
|
||||
* - playerEvents [] // optionally empty
|
||||
* - recommendedClaimsIndexClicked [] // optionally empty
|
||||
* - UserId
|
||||
* - pageLoadedAt
|
||||
* - isEmbed
|
||||
* - pageExitedAt
|
||||
* - recsysId // optional
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function: onClickedRecommended()
|
||||
* Called when RecommendedContent was clicked.
|
||||
* Adds index of clicked recommendation to parent entry
|
||||
* Adds new Entry with parentUuid for destination page
|
||||
* @param parentClaimId: string,
|
||||
* @param newClaimId: string,
|
||||
*/
|
||||
onClickedRecommended: function (parentClaimId, newClaimId) {
|
||||
const parentEntry = recsys.entries[parentClaimId] ? recsys.entries[parentClaimId] : null;
|
||||
const parentUuid = parentEntry['uuid'];
|
||||
const parentRecommendedClaims = parentEntry['recClaimIds'] || [];
|
||||
const parentClickedIndexes = parentEntry['recClickedVideoIdx'] || [];
|
||||
const indexClicked = parentRecommendedClaims.indexOf(newClaimId);
|
||||
|
||||
if (parentUuid) {
|
||||
recsys.createRecsysEntry(newClaimId, parentUuid);
|
||||
}
|
||||
parentClickedIndexes.push(indexClicked);
|
||||
recsys.log('onClickedRecommended', { parentClaimId, newClaimId });
|
||||
},
|
||||
|
||||
/**
|
||||
* Page was loaded. Get or Create entry and populate it with default data, plus recommended content, recsysId, etc.
|
||||
* Called from recommendedContent component
|
||||
*/
|
||||
onRecsLoaded: function (claimId, uris) {
|
||||
if (window.store) {
|
||||
const state = window.store.getState();
|
||||
if (!recsys.entries[claimId]) {
|
||||
recsys.createRecsysEntry(claimId);
|
||||
}
|
||||
const claimIds = getClaimIdsFromUris(uris);
|
||||
recsys.entries[claimId]['recsysId'] = makeSelectRecommendedRecsysIdForClaimId(claimId)(state) || recsysId;
|
||||
recsys.entries[claimId]['pageLoadedAt'] = Date.now();
|
||||
recsys.entries[claimId]['recClaimIds'] = claimIds;
|
||||
}
|
||||
recsys.log('onRecsLoaded', claimId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an Entry with optional parentUuid
|
||||
* @param: claimId: string
|
||||
* @param: parentUuid: string (optional)
|
||||
*/
|
||||
createRecsysEntry: function (claimId, parentUuid) {
|
||||
if (window.store && claimId) {
|
||||
const state = window.store.getState();
|
||||
const user = selectUser(state);
|
||||
const userId = user ? user.id : null;
|
||||
if (parentUuid) {
|
||||
// Make a stub entry that will be filled out on page load
|
||||
recsys.entries[claimId] = {
|
||||
uuid: Uuidv4(),
|
||||
parentUuid: parentUuid,
|
||||
uid: userId || null, // selectUser
|
||||
claimId: claimId,
|
||||
recClickedVideoIdx: [],
|
||||
pageLoadedAt: Date.now(),
|
||||
events: [],
|
||||
};
|
||||
} else {
|
||||
recsys.entries[claimId] = {
|
||||
uuid: Uuidv4(),
|
||||
uid: userId, // selectUser
|
||||
claimId: claimId,
|
||||
pageLoadedAt: Date.now(),
|
||||
recsysId: null,
|
||||
recClaimIds: [],
|
||||
recClickedVideoIdx: [],
|
||||
events: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
recsys.log('createRecsysEntry', claimId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send event for claimId
|
||||
* @param claimId
|
||||
* @param isTentative
|
||||
*/
|
||||
sendRecsysEntry: function (claimId, isTentative) {
|
||||
const shareTelemetry =
|
||||
IS_WEB || (window && window.store && selectDaemonSettings(window.store.getState()).share_usage_data);
|
||||
|
||||
if (recsys.entries[claimId] && shareTelemetry) {
|
||||
const data = JSON.stringify(recsys.entries[claimId]);
|
||||
try {
|
||||
navigator.sendBeacon(recsysEndpoint, data);
|
||||
if (!isTentative) {
|
||||
delete recsys.entries[claimId];
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('no beacon for you', error);
|
||||
}
|
||||
}
|
||||
recsys.log('sendRecsysEntry', claimId);
|
||||
},
|
||||
|
||||
/**
|
||||
* A player event fired. Get the Entry for the claimId, and add the events
|
||||
* @param claimId
|
||||
* @param event
|
||||
*/
|
||||
onRecsysPlayerEvent: function (claimId, event, isEmbedded) {
|
||||
if (!recsys.entries[claimId]) {
|
||||
recsys.createRecsysEntry(claimId);
|
||||
// do something to show it's floating or autoplay
|
||||
}
|
||||
if (isEmbedded) {
|
||||
recsys.entries[claimId]['isEmbed'] = true;
|
||||
}
|
||||
recsys.entries[claimId].events.push(event);
|
||||
recsys.log('onRecsysPlayerEvent', claimId);
|
||||
},
|
||||
log: function (callName, claimId) {
|
||||
if (recsys.debug) {
|
||||
console.log(`Call: ***${callName}***, ClaimId: ${claimId}, Recsys Entries`, Object.assign({}, recsys.entries));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Player closed. Check to see if primaryUri = playingUri
|
||||
* if so, send the Entry.
|
||||
*/
|
||||
onPlayerDispose: function (claimId, isEmbedded) {
|
||||
if (window.store) {
|
||||
const state = window.store.getState();
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const primaryUri = selectPrimaryUri(state);
|
||||
const onFilePage = playingUri === primaryUri;
|
||||
if (!onFilePage || isEmbedded) {
|
||||
if (isEmbedded) {
|
||||
recsys.entries[claimId]['isEmbed'] = true;
|
||||
}
|
||||
recsys.sendRecsysEntry(claimId);
|
||||
}
|
||||
}
|
||||
recsys.log('PlayerDispose', claimId);
|
||||
},
|
||||
|
||||
// /**
|
||||
// * File page unmount or change event
|
||||
// * Check to see if playingUri, floatingEnabled, primaryUri === playingUri
|
||||
// * If not, send the Entry.
|
||||
// * If floating enabled, leaving file page will pop out player, leading to
|
||||
// * more events until player is disposed. Don't send unless floatingPlayer playingUri
|
||||
// */
|
||||
// onLeaveFilePage: function (primaryUri) {
|
||||
// if (window.store) {
|
||||
// const state = window.store.getState();
|
||||
// const claim = makeSelectClaimForUri(primaryUri)(state);
|
||||
// const claimId = claim ? claim.claim_id : null;
|
||||
// const playingUri = selectPlayingUri(state);
|
||||
// const actualPlayingUri = playingUri && playingUri.uri;
|
||||
// // const primaryUri = selectPrimaryUri(state);
|
||||
// const floatingPlayer = makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state);
|
||||
// // When leaving page, if floating player is enabled, play will continue.
|
||||
// if (claimId) {
|
||||
// recsys.entries[claimId]['pageExitedAt'] = Date.now();
|
||||
// }
|
||||
// const shouldSend =
|
||||
// (claimId && floatingPlayer && actualPlayingUri && actualPlayingUri !== primaryUri) || !floatingPlayer || !actualPlayingUri;
|
||||
// if (shouldSend) {
|
||||
// recsys.sendRecsysEntry(claimId);
|
||||
// }
|
||||
// recsys.log('LeaveFile', claimId);
|
||||
// }
|
||||
// },
|
||||
|
||||
/**
|
||||
* Navigate event
|
||||
* Send all claimIds that aren't currently playing.
|
||||
*/
|
||||
onNavigate: function () {
|
||||
if (window.store) {
|
||||
const state = window.store.getState();
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const actualPlayingUri = playingUri && playingUri.uri;
|
||||
const claim = makeSelectClaimForUri(actualPlayingUri)(state);
|
||||
const playingClaimId = claim ? claim.claim_id : null;
|
||||
// const primaryUri = selectPrimaryUri(state);
|
||||
const floatingPlayer = makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state);
|
||||
// When leaving page, if floating player is enabled, play will continue.
|
||||
Object.keys(recsys.entries).forEach((claimId) => {
|
||||
const shouldSkip = recsys.entries[claimId].parentUuid && !recsys.entries[claimId].recClaimIds;
|
||||
if (!shouldSkip && ((claimId !== playingClaimId && floatingPlayer) || !floatingPlayer)) {
|
||||
recsys.entries[claimId]['pageExitedAt'] = Date.now();
|
||||
recsys.sendRecsysEntry(claimId);
|
||||
}
|
||||
recsys.log('OnNavigate', claimId);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
// @if TARGET='web'
|
||||
document.addEventListener('visibilitychange', function logData() {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
Object.keys(recsys.entries).map((claimId) => recsys.sendRecsysEntry(claimId, true));
|
||||
}
|
||||
});
|
||||
// @endif
|
||||
|
||||
history.listen(() => {
|
||||
recsys.onNavigate();
|
||||
});
|
||||
|
||||
export default recsys;
|
|
@ -192,6 +192,7 @@
|
|||
"semver": "^5.3.0",
|
||||
"strip-markdown": "^3.0.3",
|
||||
"style-loader": "^0.23.1",
|
||||
"tape": "^5.6.0",
|
||||
"terser-webpack-plugin": "^4.2.3",
|
||||
"three-full": "^28.0.2",
|
||||
"unist-util-visit": "^2.0.3",
|
||||
|
|
|
@ -2318,5 +2318,29 @@
|
|||
"Odysee Connect --[Section in Help Page]--": "Odysee Connect",
|
||||
"Your hub has blocked this content because it subscribes to the following blocking channel:": "Your hub has blocked this content because it subscribes to the following blocking channel:",
|
||||
"Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.": "Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.",
|
||||
"Creator Comment settings": "Creator Comment settings",
|
||||
"Remote Sync Settings": "Remote Sync Settings",
|
||||
"Wallet Sync": "Wallet Sync",
|
||||
"Manage cross device sync for your wallet": "Manage cross device sync for your wallet",
|
||||
"With your email and wallet encryption password, we will register you with sync. If your wallet is not currentlyencrypted, it will be after this. Bitch.": "With your email and wallet encryption password, we will register you with sync. If your wallet is not currentlyencrypted, it will be after this. Bitch.",
|
||||
"Password Again": "Password Again",
|
||||
"Enter Email": "Enter Email",
|
||||
"Sign up": "Sign up",
|
||||
"Let's get you set up. This will require you to encrypt your wallet and remember your password.": "Let's get you set up. This will require you to encrypt your wallet and remember your password.",
|
||||
"Sign in to your sync account. Or %sign_up%.": "Sign in to your sync account. Or %sign_up%.",
|
||||
"Sign up for a sync account. Or %sign_in%.": "Sign up for a sync account. Or %sign_in%.",
|
||||
"Verifying": "Verifying",
|
||||
"We have sent you an email to verify your account.": "We have sent you an email to verify your account.",
|
||||
"Doing Math": "Doing Math",
|
||||
"Hold on, doing some math.": "Hold on, doing some math.",
|
||||
"You are signed in as %email%.": "You are signed in as %email%.",
|
||||
"Sync Provider Url": "Sync Provider Url",
|
||||
"Enter Password": "Enter Password",
|
||||
"Enter a password.": "Enter a password.",
|
||||
"Some sign-in relevant message.": "Some sign-in relevant message.",
|
||||
"Hold on, doing some math. (Registering you)": "Hold on, doing some math. (Registering you)",
|
||||
"Failed to view lbry://@rossmanngroup#a/Day-39-Gotham-City-Solutions#e, please try again. If this problem persists, visit https://lbry.com/faq/support for support.": "Failed to view lbry://@rossmanngroup#a/Day-39-Gotham-City-Solutions#e, please try again. If this problem persists, visit https://lbry.com/faq/support for support.",
|
||||
"Anon --[used in <%anonymous% Reposted>]--": "Anon",
|
||||
"%anonymous%": "%anonymous%",
|
||||
"--end--": "--end--"
|
||||
}
|
||||
|
|
|
@ -51,7 +51,6 @@ type Analytics = {
|
|||
) => Promise<any>,
|
||||
emailProvidedEvent: () => void,
|
||||
emailVerifiedEvent: () => void,
|
||||
rewardEligibleEvent: () => void,
|
||||
startupEvent: () => void,
|
||||
purchaseEvent: (number) => void,
|
||||
readyEvent: (number) => void,
|
||||
|
@ -355,9 +354,6 @@ const analytics: Analytics = {
|
|||
emailVerifiedEvent: () => {
|
||||
sendMatomoEvent('Engagement', 'Email-Verified');
|
||||
},
|
||||
rewardEligibleEvent: () => {
|
||||
sendMatomoEvent('Engagement', 'Reward-Eligible');
|
||||
},
|
||||
openUrlEvent: (url: string) => {
|
||||
sendMatomoEvent('Engagement', 'Open-Url', url);
|
||||
},
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { hot } from 'react-hot-loader/root';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectGetSyncErrorMessage, selectSyncFatalError } from 'redux/selectors/sync';
|
||||
import { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user';
|
||||
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
||||
import { doFetchChannelListMine, doFetchCollectionListMine, doResolveUris } from 'redux/actions/claims';
|
||||
import { selectMyChannelUrls, selectMyChannelClaimIds } from 'redux/selectors/claims';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
|
@ -25,7 +22,7 @@ import { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings
|
|||
import { doSyncLoop } from 'redux/actions/sync';
|
||||
import {
|
||||
doDownloadUpgradeRequested,
|
||||
doSignIn,
|
||||
doSignIn, // huh
|
||||
doGetAndPopulatePreferences,
|
||||
doSetActiveChannel,
|
||||
doSetIncognito,
|
||||
|
@ -34,8 +31,6 @@ import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/c
|
|||
import App from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
user: selectUser(state),
|
||||
accessToken: selectAccessToken(state),
|
||||
theme: selectThemePath(state),
|
||||
language: selectLanguage(state),
|
||||
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
|
||||
|
@ -43,8 +38,6 @@ const select = (state) => ({
|
|||
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
|
||||
isUpgradeAvailable: selectIsUpgradeAvailable(state),
|
||||
syncError: selectGetSyncErrorMessage(state),
|
||||
rewards: selectUnclaimedRewards(state),
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
currentModal: selectModal(state),
|
||||
syncFatalError: selectSyncFatalError(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
|
@ -55,7 +48,6 @@ const select = (state) => ({
|
|||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
||||
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
||||
fetchCollectionListMine: () => dispatch(doFetchCollectionListMine()),
|
||||
setLanguage: (language) => dispatch(doSetLanguage(language)),
|
||||
|
@ -64,7 +56,6 @@ const perform = (dispatch) => ({
|
|||
updatePreferences: () => dispatch(doGetAndPopulatePreferences()),
|
||||
getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()),
|
||||
syncLoop: (noInterval) => dispatch(doSyncLoop(noInterval)),
|
||||
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
|
||||
setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()),
|
||||
setIncognito: () => dispatch(doSetIncognito()),
|
||||
fetchModBlockedList: () => dispatch(doFetchModBlockedList()),
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React, { useEffect, useRef, useState, useLayoutEffect } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import analytics from 'analytics';
|
||||
import Router from 'component/router/index';
|
||||
import ReactModal from 'react-modal';
|
||||
import { openContextMenu } from 'util/context-menu';
|
||||
import useKonamiListener from 'util/enhanced-layout';
|
||||
import FileRenderFloating from 'component/fileRenderFloating';
|
||||
import { withRouter } from 'react-router';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
import REWARDS from 'rewards';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import LANGUAGES from 'constants/languages';
|
||||
import useZoom from 'effects/use-zoom';
|
||||
|
@ -46,7 +42,6 @@ type Props = {
|
|||
length: number,
|
||||
push: (string) => void,
|
||||
},
|
||||
fetchAccessToken: () => void,
|
||||
fetchChannelListMine: () => void,
|
||||
fetchCollectionListMine: () => void,
|
||||
signIn: () => void,
|
||||
|
@ -82,27 +77,18 @@ type Props = {
|
|||
function App(props: Props) {
|
||||
const {
|
||||
theme,
|
||||
user,
|
||||
fetchAccessToken,
|
||||
fetchChannelListMine,
|
||||
fetchCollectionListMine,
|
||||
signIn,
|
||||
autoUpdateDownloaded,
|
||||
isUpgradeAvailable,
|
||||
requestDownloadUpgrade,
|
||||
uploadCount,
|
||||
history,
|
||||
syncError,
|
||||
language,
|
||||
languages,
|
||||
setLanguage,
|
||||
updatePreferences,
|
||||
getWalletSyncPref,
|
||||
rewards,
|
||||
setReferrer,
|
||||
isAuthenticated,
|
||||
syncLoop,
|
||||
currentModal,
|
||||
syncFatalError,
|
||||
myChannelClaimIds,
|
||||
activeChannelId,
|
||||
|
@ -117,38 +103,16 @@ function App(props: Props) {
|
|||
|
||||
const appRef = useRef();
|
||||
const isEnhancedLayout = useKonamiListener();
|
||||
const [hasSignedIn, setHasSignedIn] = useState(false);
|
||||
const [readyForSync, setReadyForSync] = useState(false);
|
||||
const [readyForPrefs, setReadyForPrefs] = useState(false);
|
||||
const hasVerifiedEmail = user && Boolean(user.has_verified_email);
|
||||
const isRewardApproved = user && user.is_reward_approved;
|
||||
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
|
||||
const previousRewardApproved = usePrevious(isRewardApproved);
|
||||
const { pathname, search } = props.location;
|
||||
const [upgradeNagClosed, setUpgradeNagClosed] = useState(false);
|
||||
const [resolvedSubscriptions, setResolvedSubscriptions] = useState(false);
|
||||
// const [retryingSync, setRetryingSync] = useState(false);
|
||||
const [langRenderKey, setLangRenderKey] = useState(0);
|
||||
const [sidebarOpen] = usePersistedState('sidebar', true);
|
||||
const showUpgradeButton = (autoUpdateDownloaded || isUpgradeAvailable) && !upgradeNagClosed;
|
||||
// referral claiming
|
||||
const referredRewardAvailable = rewards && rewards.some((reward) => reward.reward_type === REWARDS.TYPE_REFEREE);
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const rawReferrerParam = urlParams.get('r');
|
||||
const sanitizedReferrerParam = rawReferrerParam && rawReferrerParam.replace(':', '#');
|
||||
const userId = user && user.id;
|
||||
const useCustomScrollbar = !IS_MAC;
|
||||
const hasMyChannels = myChannelClaimIds && myChannelClaimIds.length > 0;
|
||||
const hasNoChannels = myChannelClaimIds && myChannelClaimIds.length === 0;
|
||||
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
|
||||
const hasActiveChannelClaim = activeChannelId !== undefined;
|
||||
const isPersonalized = hasVerifiedEmail;
|
||||
|
||||
useEffect(() => {
|
||||
if (userId) {
|
||||
analytics.setUser(userId);
|
||||
}
|
||||
}, [userId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!uploadCount) return;
|
||||
|
@ -188,23 +152,10 @@ function App(props: Props) {
|
|||
}, []);
|
||||
|
||||
// Enable ctrl +/- zooming on Desktop.
|
||||
// @if TARGET='app'
|
||||
useZoom();
|
||||
// @endif
|
||||
|
||||
// Enable 'Alt + Left/Right' for history navigation on Desktop.
|
||||
// @if TARGET='app'
|
||||
useHistoryNav(history);
|
||||
// @endif
|
||||
|
||||
useEffect(() => {
|
||||
if (referredRewardAvailable && sanitizedReferrerParam && isRewardApproved) {
|
||||
setReferrer(sanitizedReferrerParam, true);
|
||||
} else if (referredRewardAvailable && sanitizedReferrerParam) {
|
||||
setReferrer(sanitizedReferrerParam, false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sanitizedReferrerParam, isRewardApproved, referredRewardAvailable]);
|
||||
|
||||
useEffect(() => {
|
||||
const { current: wrapperElement } = appRef;
|
||||
|
@ -212,13 +163,9 @@ function App(props: Props) {
|
|||
ReactModal.setAppElement(wrapperElement);
|
||||
}
|
||||
|
||||
fetchAccessToken();
|
||||
|
||||
// @if TARGET='app'
|
||||
fetchChannelListMine(); // This is fetched after a user is signed in on web
|
||||
fetchCollectionListMine();
|
||||
// @endif
|
||||
}, [appRef, fetchAccessToken, fetchChannelListMine, fetchCollectionListMine]);
|
||||
}, [appRef, fetchChannelListMine, fetchCollectionListMine]);
|
||||
|
||||
useEffect(() => {
|
||||
// $FlowFixMe
|
||||
|
@ -261,71 +208,20 @@ function App(props: Props) {
|
|||
}, [shouldMigrateLanguage, setLanguage]);
|
||||
|
||||
useEffect(() => {
|
||||
// Check that previousHasVerifiedEmail was not undefined instead of just not truthy
|
||||
// This ensures we don't fire the emailVerified event on the initial user fetch
|
||||
if (previousHasVerifiedEmail === false && hasVerifiedEmail) {
|
||||
analytics.emailVerifiedEvent();
|
||||
if (updatePreferences && getWalletSyncPref) {
|
||||
getWalletSyncPref().then(() => updatePreferences());
|
||||
}
|
||||
}, [previousHasVerifiedEmail, hasVerifiedEmail, signIn]);
|
||||
|
||||
useEffect(() => {
|
||||
if (previousRewardApproved === false && isRewardApproved) {
|
||||
analytics.rewardEligibleEvent();
|
||||
}
|
||||
}, [previousRewardApproved, isRewardApproved]);
|
||||
|
||||
useEffect(() => {
|
||||
if (updatePreferences && getWalletSyncPref && readyForPrefs) {
|
||||
getWalletSyncPref()
|
||||
.then(() => updatePreferences())
|
||||
.then(() => {
|
||||
setReadyForSync(true);
|
||||
});
|
||||
}
|
||||
}, [updatePreferences, getWalletSyncPref, setReadyForSync, readyForPrefs, hasVerifiedEmail]);
|
||||
|
||||
// ready for sync syncs, however after signin when hasVerifiedEmail, that syncs too.
|
||||
useEffect(() => {
|
||||
// signInSyncPref is cleared after sharedState loop.
|
||||
if (readyForSync && hasVerifiedEmail) {
|
||||
// In case we are syncing.
|
||||
syncLoop();
|
||||
}
|
||||
}, [readyForSync, hasVerifiedEmail, syncLoop]);
|
||||
|
||||
// We know someone is logging in or not when we get their user object
|
||||
// We'll use this to determine when it's time to pull preferences
|
||||
// This will no longer work if desktop users no longer get a user object from lbryinc
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
setReadyForPrefs(true);
|
||||
}
|
||||
}, [user, setReadyForPrefs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (syncError && isAuthenticated && !pathname.includes(PAGES.AUTH_WALLET_PASSWORD) && !currentModal) {
|
||||
history.push(`/$/${PAGES.AUTH_WALLET_PASSWORD}?redirect=${pathname}`);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [syncError, pathname, isAuthenticated]);
|
||||
|
||||
// Keep this at the end to ensure initial setup effects are run first
|
||||
useEffect(() => {
|
||||
if (!hasSignedIn && hasVerifiedEmail) {
|
||||
signIn();
|
||||
setHasSignedIn(true);
|
||||
}
|
||||
}, [hasVerifiedEmail, signIn, hasSignedIn]);
|
||||
}, [updatePreferences, getWalletSyncPref]);
|
||||
|
||||
// batch resolve subscriptions to be used by the sideNavigation component.
|
||||
// add it here so that it only resolves the first time, despite route changes.
|
||||
// useLayoutEffect because it has to be executed before the sideNavigation component requests them
|
||||
useLayoutEffect(() => {
|
||||
if (sidebarOpen && isPersonalized && subscriptions && !resolvedSubscriptions) {
|
||||
if (sidebarOpen && subscriptions && !resolvedSubscriptions) {
|
||||
setResolvedSubscriptions(true);
|
||||
resolveUris(subscriptions.map((sub) => sub.uri));
|
||||
}
|
||||
}, [sidebarOpen, isPersonalized, resolvedSubscriptions, subscriptions, resolveUris, setResolvedSubscriptions]);
|
||||
}, [sidebarOpen, resolvedSubscriptions, subscriptions, resolveUris, setResolvedSubscriptions]);
|
||||
|
||||
useEffect(() => {
|
||||
// When language is changed or translations are fetched, we render.
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import Button from './view';
|
||||
import React, { forwardRef } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
pathname: state.router.location.pathname,
|
||||
emailVerified: selectUserVerifiedEmail(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const ConnectedButton = connect(mapStateToProps)(Button);
|
||||
|
|
|
@ -32,11 +32,9 @@ type Props = {
|
|||
onMouseEnter: ?(any) => any,
|
||||
onMouseLeave: ?(any) => any,
|
||||
pathname: string,
|
||||
emailVerified: boolean,
|
||||
myref: any,
|
||||
dispatch: any,
|
||||
'aria-label'?: string,
|
||||
user: ?User,
|
||||
};
|
||||
|
||||
// use forwardRef to allow consumers to pass refs to the button content if they want to
|
||||
|
@ -63,11 +61,9 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
iconSize,
|
||||
iconColor,
|
||||
activeClass,
|
||||
emailVerified,
|
||||
myref,
|
||||
dispatch, // <button> doesn't know what to do with dispatch
|
||||
pathname,
|
||||
user,
|
||||
authSrc,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUserEmail } from 'redux/selectors/user';
|
||||
import CardVerify from './view';
|
||||
|
||||
const select = state => ({
|
||||
email: selectUserEmail(state),
|
||||
});
|
||||
|
||||
const perform = () => ({});
|
||||
|
||||
export default connect(select, perform)(CardVerify);
|
|
@ -1,185 +0,0 @@
|
|||
/* eslint-disable no-undef */
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
|
||||
let scriptLoading = false;
|
||||
let scriptLoaded = false;
|
||||
let scriptDidError = false;
|
||||
|
||||
// Flow does not like the way this stripe plugin works
|
||||
// Disabled because it was a huge pain
|
||||
// type Props = {
|
||||
// disabled: boolean,
|
||||
// label: ?string,
|
||||
// email: string,
|
||||
|
||||
// // =====================================================
|
||||
// // Required by stripe
|
||||
// // see Stripe docs for more info:
|
||||
// // https://stripe.com/docs/checkout#integration-custom
|
||||
// // =====================================================
|
||||
|
||||
// // Your publishable key (test or live).
|
||||
// // can't use "key" as a prop in react, so have to change the keyname
|
||||
// stripeKey: string,
|
||||
|
||||
// // The callback to invoke when the Checkout process is complete.
|
||||
// // function(token)
|
||||
// // token is the token object created.
|
||||
// // token.id can be used to create a charge or customer.
|
||||
// // token.email contains the email address entered by the user.
|
||||
// token: string,
|
||||
// };
|
||||
|
||||
class CardVerify extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
open: false,
|
||||
scriptFailedToLoad: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (scriptLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scriptLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
scriptLoading = true;
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://checkout.stripe.com/checkout.js';
|
||||
script.async = true;
|
||||
|
||||
this.loadPromise = (() => {
|
||||
let canceled = false;
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
script.onload = () => {
|
||||
scriptLoaded = true;
|
||||
scriptLoading = false;
|
||||
resolve();
|
||||
this.onScriptLoaded();
|
||||
};
|
||||
script.onerror = event => {
|
||||
scriptDidError = true;
|
||||
scriptLoading = false;
|
||||
reject(event);
|
||||
this.onScriptError(event);
|
||||
};
|
||||
});
|
||||
const wrappedPromise = new Promise((resolve, reject) => {
|
||||
promise.then(() => (canceled ? reject({ isCanceled: true }) : resolve()));
|
||||
promise.catch(error => (canceled ? reject({ isCanceled: true }) : reject(error)));
|
||||
});
|
||||
|
||||
return {
|
||||
promise: wrappedPromise,
|
||||
reject() {
|
||||
canceled = true;
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
this.loadPromise.promise.then(this.onScriptLoaded).catch(this.onScriptError);
|
||||
|
||||
// $FlowFixMe
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (!scriptLoading) {
|
||||
this.updateStripeHandler();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.loadPromise) {
|
||||
this.loadPromise.reject();
|
||||
}
|
||||
if (CardVerify.stripeHandler && this.state.open) {
|
||||
CardVerify.stripeHandler.close();
|
||||
}
|
||||
}
|
||||
|
||||
onScriptLoaded = () => {
|
||||
if (!CardVerify.stripeHandler) {
|
||||
CardVerify.stripeHandler = StripeCheckout.configure({
|
||||
key: this.props.stripeKey,
|
||||
});
|
||||
|
||||
if (this.hasPendingClick) {
|
||||
this.showStripeDialog();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onScriptError = (...args) => {
|
||||
this.setState({ scriptFailedToLoad: true });
|
||||
};
|
||||
|
||||
onClosed = () => {
|
||||
this.setState({ open: false });
|
||||
};
|
||||
|
||||
updateStripeHandler() {
|
||||
if (!CardVerify.stripeHandler) {
|
||||
CardVerify.stripeHandler = StripeCheckout.configure({
|
||||
key: this.props.stripeKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showStripeDialog() {
|
||||
this.setState({ open: true });
|
||||
CardVerify.stripeHandler.open({
|
||||
allowRememberMe: false,
|
||||
closed: this.onClosed,
|
||||
description: __('Confirm Identity'),
|
||||
email: this.props.email,
|
||||
locale: 'auto',
|
||||
panelLabel: 'Verify',
|
||||
token: this.props.token,
|
||||
zipCode: true,
|
||||
});
|
||||
}
|
||||
|
||||
onClick = () => {
|
||||
if (scriptDidError) {
|
||||
try {
|
||||
throw new Error('Tried to call onClick, but StripeCheckout failed to load');
|
||||
} catch (x) {}
|
||||
} else if (CardVerify.stripeHandler) {
|
||||
this.showStripeDialog();
|
||||
} else {
|
||||
this.hasPendingClick = true;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { scriptFailedToLoad } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{scriptFailedToLoad && (
|
||||
<div className="error__text">There was an error connecting to Stripe. Please try again later.</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
button="primary"
|
||||
label={this.props.label}
|
||||
disabled={this.props.disabled || this.state.open || this.hasPendingClick}
|
||||
onClick={this.onClick.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CardVerify;
|
||||
/* eslint-enable no-undef */
|
||||
/* eslint-enable react/prop-types */
|
|
@ -11,7 +11,6 @@ import { doResolveUris } from 'redux/actions/claims';
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||
import { withRouter } from 'react-router';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { makeSelectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
|
||||
|
||||
import ChannelContent from './view';
|
||||
|
@ -29,7 +28,6 @@ const select = (state, props) => {
|
|||
channelIsMine: selectClaimIsMine(state, claim),
|
||||
channelIsBlocked: makeSelectChannelIsMuted(props.uri)(state),
|
||||
claim,
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
showMature: selectShowMatureContent(state),
|
||||
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
|
||||
};
|
||||
|
|
|
@ -26,7 +26,6 @@ type Props = {
|
|||
defaultPageSize?: number,
|
||||
defaultInfiniteScroll?: Boolean,
|
||||
claim: Claim,
|
||||
isAuthenticated: boolean,
|
||||
showMature: boolean,
|
||||
tileLayout: boolean,
|
||||
viewHiddenChannels: boolean,
|
||||
|
|
|
@ -15,8 +15,6 @@ import { selectBalance } from 'redux/selectors/wallet';
|
|||
import { doUpdateChannel, doCreateChannel, doClearChannelErrors } from 'redux/actions/claims';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { doUpdateBlockListForPublishedChannel } from 'redux/actions/comments';
|
||||
import { doClaimInitialRewards } from 'redux/actions/rewards';
|
||||
import { selectIsClaimingInitialRewards, selectHasClaimedInitialRewards } from 'redux/selectors/rewards';
|
||||
import ChannelForm from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -36,8 +34,6 @@ const select = (state, props) => ({
|
|||
createError: selectCreateChannelError(state),
|
||||
creatingChannel: selectCreatingChannel(state),
|
||||
balance: selectBalance(state),
|
||||
isClaimingInitialRewards: selectIsClaimingInitialRewards(state),
|
||||
hasClaimedInitialRewards: selectHasClaimedInitialRewards(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
|
@ -52,7 +48,6 @@ const perform = (dispatch) => ({
|
|||
);
|
||||
},
|
||||
clearChannelErrors: () => dispatch(doClearChannelErrors()),
|
||||
claimInitialRewards: () => dispatch(doClaimInitialRewards()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(ChannelForm);
|
||||
|
|
|
@ -51,7 +51,6 @@ type Props = {
|
|||
createError: string,
|
||||
creatingChannel: boolean,
|
||||
clearChannelErrors: () => void,
|
||||
claimInitialRewards: () => void,
|
||||
onDone: () => void,
|
||||
openModal: (
|
||||
id: string,
|
||||
|
@ -59,8 +58,6 @@ type Props = {
|
|||
) => void,
|
||||
uri: string,
|
||||
disabled: boolean,
|
||||
isClaimingInitialRewards: boolean,
|
||||
hasClaimedInitialRewards: boolean,
|
||||
};
|
||||
|
||||
function ChannelForm(props: Props) {
|
||||
|
@ -85,11 +82,8 @@ function ChannelForm(props: Props) {
|
|||
creatingChannel,
|
||||
createError,
|
||||
clearChannelErrors,
|
||||
claimInitialRewards,
|
||||
openModal,
|
||||
disabled,
|
||||
isClaimingInitialRewards,
|
||||
hasClaimedInitialRewards,
|
||||
} = props;
|
||||
const [nameError, setNameError] = React.useState(undefined);
|
||||
const [bidError, setBidError] = React.useState('');
|
||||
|
@ -107,21 +101,11 @@ function ChannelForm(props: Props) {
|
|||
const primaryLanguage = Array.isArray(languageParam) && languageParam.length && languageParam[0];
|
||||
const secondaryLanguage = Array.isArray(languageParam) && languageParam.length >= 2 && languageParam[1];
|
||||
const submitLabel = React.useMemo(() => {
|
||||
if (isClaimingInitialRewards) {
|
||||
return __('Claiming credits...');
|
||||
}
|
||||
return creatingChannel || updatingChannel ? __('Submitting...') : __('Submit');
|
||||
}, [isClaimingInitialRewards, creatingChannel, updatingChannel]);
|
||||
}, [creatingChannel, updatingChannel]);
|
||||
const submitDisabled = React.useMemo(() => {
|
||||
return (
|
||||
isClaimingInitialRewards ||
|
||||
creatingChannel ||
|
||||
updatingChannel ||
|
||||
coverError ||
|
||||
bidError ||
|
||||
(isNewChannel && !params.name)
|
||||
);
|
||||
}, [isClaimingInitialRewards, creatingChannel, updatingChannel, nameError, bidError, isNewChannel, params.name]);
|
||||
return creatingChannel || updatingChannel || coverError || bidError || (isNewChannel && !params.name);
|
||||
}, [creatingChannel, updatingChannel, nameError, bidError, isNewChannel, params.name]);
|
||||
|
||||
function getChannelParams() {
|
||||
// fill this in with sdk data
|
||||
|
@ -255,12 +239,6 @@ function ChannelForm(props: Props) {
|
|||
clearChannelErrors();
|
||||
}, [clearChannelErrors]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!hasClaimedInitialRewards) {
|
||||
claimInitialRewards();
|
||||
}
|
||||
}, [hasClaimedInitialRewards, claimInitialRewards]);
|
||||
|
||||
const coverSrc = coverError ? ThumbnailBrokenImage : coverPreview;
|
||||
|
||||
let thumbnailPreview;
|
||||
|
|
|
@ -10,7 +10,6 @@ import { doCommentUpdate, doCommentList } from 'redux/actions/comments';
|
|||
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import { doClearPlayingUri } from 'redux/actions/content';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import {
|
||||
selectLinkedCommentAncestors,
|
||||
selectOthersReactsForComment,
|
||||
|
@ -33,7 +32,6 @@ const select = (state, props) => {
|
|||
claim: makeSelectClaimForUri(uri)(state),
|
||||
thumbnail: author_uri && selectThumbnailForUri(state, author_uri),
|
||||
channelIsBlocked: author_uri && makeSelectChannelIsMuted(author_uri)(state),
|
||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||
othersReacts: selectOthersReactsForComment(state, reactionKey),
|
||||
activeChannelClaim,
|
||||
hasChannels: selectHasChannels(state),
|
||||
|
|
|
@ -49,7 +49,6 @@ type Props = {
|
|||
linkedCommentId?: string,
|
||||
linkedCommentAncestors: { [string]: Array<string> },
|
||||
hasChannels: boolean,
|
||||
commentingEnabled: boolean,
|
||||
doToast: ({ message: string }) => void,
|
||||
isTopLevel?: boolean,
|
||||
threadDepth: number,
|
||||
|
@ -82,7 +81,6 @@ function CommentView(props: Props) {
|
|||
totalReplyPages,
|
||||
linkedCommentId,
|
||||
linkedCommentAncestors,
|
||||
commentingEnabled,
|
||||
hasChannels,
|
||||
doToast,
|
||||
isTopLevel,
|
||||
|
@ -368,7 +366,7 @@ function CommentView(props: Props) {
|
|||
<div className="comment__actions">
|
||||
{threadDepth !== 0 && (
|
||||
<Button
|
||||
label={commentingEnabled ? __('Reply') : __('Log in to reply')}
|
||||
label={__('Reply')}
|
||||
className="comment__action"
|
||||
onClick={handleCommentReply}
|
||||
icon={ICONS.REPLY}
|
||||
|
|
|
@ -157,31 +157,6 @@ export default function CreatorAnalytics(props: Props) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* <Card
|
||||
iconColor
|
||||
className="section"
|
||||
title={<span>{__('%lbc_received% LBRY Credits Earned', { lbc_received: stats.AllLBCReceived })}</span>}
|
||||
icon={ICONS.REWARDS}
|
||||
subtitle={
|
||||
<React.Fragment>
|
||||
<div className="card__data-subtitle">
|
||||
<span>
|
||||
{'+'}{' '}
|
||||
{__('%lbc_received_changed% this week', {
|
||||
lbc_received_changed: stats.LBCReceivedChange || 0,
|
||||
})}
|
||||
</span>
|
||||
{stats.LBCReceivedChange > 0 && <Icon icon={ICONS.TRENDING} iconColor="green" size={18} />}
|
||||
</div>
|
||||
<p className="help">
|
||||
{__(
|
||||
"Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance."
|
||||
)}
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
/> */}
|
||||
|
||||
{stats.VideoURITopNew ? (
|
||||
<Card
|
||||
className="section"
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { selectEmailToVerify, selectUser } from 'redux/selectors/user';
|
||||
import FirstRunEmailCollection from './view';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
|
||||
const select = (state) => ({
|
||||
emailCollectionAcknowledged: makeSelectClientSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED)(state),
|
||||
email: selectEmailToVerify(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => () => ({
|
||||
acknowledgeEmail: () => {
|
||||
dispatch(doSetClientSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED, true));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FirstRunEmailCollection);
|
|
@ -1,40 +0,0 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import UserEmailNew from 'component/userEmailNew';
|
||||
import UserEmailVerify from 'component/userEmailVerify';
|
||||
|
||||
type Props = {
|
||||
email: string,
|
||||
emailCollectionAcknowledged: boolean,
|
||||
user: ?{ has_verified_email: boolean },
|
||||
completeFirstRun: () => void,
|
||||
acknowledgeEmail: () => void,
|
||||
};
|
||||
|
||||
class FirstRunEmailCollection extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { completeFirstRun, email, user, emailCollectionAcknowledged, acknowledgeEmail } = this.props;
|
||||
|
||||
// this shouldn't happen
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cancelButton = <Button button="link" onClick={completeFirstRun} label={__('Not Now')} />;
|
||||
if (user && !user.has_verified_email && !email) {
|
||||
return <UserEmailNew cancelButton={cancelButton} />;
|
||||
} else if (user && !user.has_verified_email) {
|
||||
return <UserEmailVerify cancelButton={cancelButton} />;
|
||||
}
|
||||
|
||||
// Try to acknowledge here so users don't see an empty email screen in the first run banner
|
||||
if (!emailCollectionAcknowledged) {
|
||||
acknowledgeEmail();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default FirstRunEmailCollection;
|
|
@ -1,7 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, makeSelectContentTypeForUri, makeSelectMetadataForUri } from 'redux/selectors/claims';
|
||||
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
||||
import { selectUser } from 'redux/selectors/user';
|
||||
import { doOpenFileInFolder } from 'redux/actions/file';
|
||||
import FileDetails from './view';
|
||||
|
||||
|
@ -10,7 +9,6 @@ const select = (state, props) => ({
|
|||
contentType: makeSelectContentTypeForUri(props.uri)(state),
|
||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||
metadata: makeSelectMetadataForUri(props.uri)(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
|
|
|
@ -10,7 +10,6 @@ type Props = {
|
|||
metadata: StreamMetadata,
|
||||
openFolder: (string) => void,
|
||||
contentType: string,
|
||||
user: ?any,
|
||||
};
|
||||
|
||||
class FileDetails extends PureComponent<Props> {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectFileInfoForUri, makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
|
||||
import { makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
||||
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
||||
import { makeSelectFileRenderModeForUri, selectPrimaryUri } from 'redux/selectors/content';
|
||||
import { withRouter } from 'react-router';
|
||||
import { doAnalyticsView } from 'redux/actions/app';
|
||||
|
@ -19,7 +18,6 @@ const select = (state, props) => ({
|
|||
|
||||
const perform = (dispatch) => ({
|
||||
triggerAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
||||
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||
});
|
||||
|
||||
export default withRouter(connect(select, perform)(FileRenderInline));
|
||||
|
|
|
@ -11,23 +11,13 @@ type Props = {
|
|||
renderMode: string,
|
||||
streamingUrl?: string,
|
||||
triggerAnalyticsView: (string, number) => Promise<any>,
|
||||
claimRewards: () => void,
|
||||
costInfo: any,
|
||||
claimWasPurchased: boolean,
|
||||
};
|
||||
|
||||
export default function FileRenderInline(props: Props) {
|
||||
const {
|
||||
isPlaying,
|
||||
fileInfo,
|
||||
uri,
|
||||
streamingUrl,
|
||||
triggerAnalyticsView,
|
||||
claimRewards,
|
||||
renderMode,
|
||||
costInfo,
|
||||
claimWasPurchased,
|
||||
} = props;
|
||||
const { isPlaying, fileInfo, uri, streamingUrl, triggerAnalyticsView, renderMode, costInfo, claimWasPurchased } =
|
||||
props;
|
||||
const [playTime, setPlayTime] = useState();
|
||||
const isFree = !costInfo || (costInfo.cost !== undefined && costInfo.cost === 0);
|
||||
const isReadyToView = fileInfo && fileInfo.completed;
|
||||
|
@ -55,11 +45,10 @@ export default function FileRenderInline(props: Props) {
|
|||
const timeToStart = Date.now() - playTime;
|
||||
|
||||
triggerAnalyticsView(uri, timeToStart).then(() => {
|
||||
claimRewards();
|
||||
setPlayTime(null);
|
||||
});
|
||||
}
|
||||
}, [setPlayTime, claimRewards, triggerAnalyticsView, isReadyToPlay, playTime, uri]);
|
||||
}, [setPlayTime, triggerAnalyticsView, isReadyToPlay, playTime, uri]);
|
||||
|
||||
if (!isPlaying) {
|
||||
return null;
|
||||
|
|
|
@ -1,29 +1,25 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
|
||||
import { doSignOut } from 'redux/actions/app';
|
||||
import { formatCredits } from 'util/format-credits';
|
||||
import { selectClientSetting } from 'redux/selectors/settings';
|
||||
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
|
||||
import { selectHasNavigated } from 'redux/selectors/app';
|
||||
import { selectTotalBalance, selectBalance } from 'redux/selectors/wallet';
|
||||
import { selectEmailToVerify, selectUser } from 'redux/selectors/user';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { doLbrysyncRegister } from 'redux/actions/sync';
|
||||
import Header from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
balance: selectBalance(state),
|
||||
emailToVerify: selectEmailToVerify(state),
|
||||
hasNavigated: selectHasNavigated(state),
|
||||
hideBalance: selectClientSetting(state, SETTINGS.HIDE_BALANCE),
|
||||
roundedBalance: formatCredits(selectTotalBalance(state), 2, true),
|
||||
roundedSpendableBalance: formatCredits(selectBalance(state), 2, true),
|
||||
syncError: selectGetSyncErrorMessage(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
clearEmailEntry: () => dispatch(doClearEmailEntry()),
|
||||
clearPasswordEntry: () => dispatch(doClearPasswordEntry()),
|
||||
lbrysyncRegister: (username, password) => dispatch(doLbrysyncRegister(username, password)),
|
||||
signOut: () => dispatch(doSignOut()),
|
||||
});
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ type Props = {
|
|||
simpleTitle: string, // Just use the same value as `title` if `title` is already short (~< 10 chars), unless you have a better idea for title overlfow on mobile
|
||||
},
|
||||
balance: number,
|
||||
emailToVerify?: string,
|
||||
hasNavigated: boolean,
|
||||
hideBalance: boolean,
|
||||
hideCancel: boolean,
|
||||
|
@ -43,8 +42,6 @@ type Props = {
|
|||
roundedSpendableBalance: string,
|
||||
sidebarOpen: boolean,
|
||||
syncError: ?string,
|
||||
clearEmailEntry: () => void,
|
||||
clearPasswordEntry: () => void,
|
||||
setSidebarOpen: (boolean) => void,
|
||||
signOut: () => void,
|
||||
};
|
||||
|
@ -54,7 +51,6 @@ const Header = (props: Props) => {
|
|||
authHeader,
|
||||
backout,
|
||||
balance,
|
||||
emailToVerify,
|
||||
hideBalance,
|
||||
hideCancel,
|
||||
history,
|
||||
|
@ -63,8 +59,6 @@ const Header = (props: Props) => {
|
|||
roundedSpendableBalance,
|
||||
sidebarOpen,
|
||||
syncError,
|
||||
clearEmailEntry,
|
||||
clearPasswordEntry,
|
||||
setSidebarOpen,
|
||||
signOut,
|
||||
} = props;
|
||||
|
@ -80,7 +74,6 @@ const Header = (props: Props) => {
|
|||
// on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
|
||||
const isVerifyPage = pathname.includes(PAGES.AUTH_VERIFY);
|
||||
const isSignUpPage = pathname.includes(PAGES.AUTH);
|
||||
const isSignInPage = pathname.includes(PAGES.AUTH_SIGNIN);
|
||||
const isPwdResetPage = pathname.includes(PAGES.AUTH_PASSWORD_RESET);
|
||||
|
||||
// For pages that allow for "backing out", shows a backout option instead of the Home logo
|
||||
|
@ -236,12 +229,9 @@ const Header = (props: Props) => {
|
|||
// className="button--header-close"
|
||||
icon={ICONS.REMOVE}
|
||||
onClick={() => {
|
||||
clearEmailEntry();
|
||||
clearPasswordEntry();
|
||||
|
||||
if (syncError) signOut();
|
||||
|
||||
if ((isSignInPage && !emailToVerify) || isSignUpPage || isPwdResetPage) {
|
||||
if (isSignUpPage || isPwdResetPage) {
|
||||
goBack();
|
||||
} else {
|
||||
push('/');
|
||||
|
|
|
@ -4,13 +4,11 @@ import { selectActiveChannelStakedLevel } from 'redux/selectors/app';
|
|||
import { selectClientSetting } from 'redux/selectors/settings';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import HeaderMenuButtons from './view';
|
||||
import { selectUser } from 'redux/selectors/user';
|
||||
|
||||
const select = (state) => ({
|
||||
activeChannelStakedLevel: selectActiveChannelStakedLevel(state),
|
||||
automaticDarkModeEnabled: selectClientSetting(state, SETTINGS.AUTOMATIC_DARK_MODE_ENABLED),
|
||||
currentTheme: selectClientSetting(state, SETTINGS.THEME),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
|
|
|
@ -14,14 +14,13 @@ import Tooltip from 'component/common/tooltip';
|
|||
type HeaderMenuButtonProps = {
|
||||
automaticDarkModeEnabled: boolean,
|
||||
currentTheme: string,
|
||||
user: ?User,
|
||||
handleThemeToggle: (boolean, string) => void,
|
||||
};
|
||||
|
||||
export default function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||
const { automaticDarkModeEnabled, currentTheme, user, handleThemeToggle } = props;
|
||||
const { automaticDarkModeEnabled, currentTheme, handleThemeToggle } = props;
|
||||
|
||||
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
|
||||
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS;
|
||||
|
||||
return (
|
||||
<div className="header__buttons">
|
||||
|
@ -35,6 +34,7 @@ export default function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
|||
<MenuList className="menu__list--header">
|
||||
<HeaderMenuLink page={PAGES.UPLOAD} icon={ICONS.PUBLISH} name={__('Upload')} />
|
||||
<HeaderMenuLink page={PAGES.CHANNEL_NEW} icon={ICONS.CHANNEL} name={__('New Channel')} />
|
||||
<HeaderMenuLink page={PAGES.SETTINGS_SYNC} icon={ICONS.GAMING} name={__('Sign In')} />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
|
||||
import { doSeeAllNotifications } from 'redux/actions/notifications';
|
||||
import { selectUser } from 'redux/selectors/user';
|
||||
import NotificationHeaderButton from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
unseenCount: selectUnseenNotificationCount(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
|
|
|
@ -13,15 +13,14 @@ import Tooltip from 'component/common/tooltip';
|
|||
|
||||
type Props = {
|
||||
unseenCount: number,
|
||||
user: ?User,
|
||||
doSeeAllNotifications: () => void,
|
||||
};
|
||||
|
||||
export default function NotificationHeaderButton(props: Props) {
|
||||
const { unseenCount, user, doSeeAllNotifications } = props;
|
||||
const { unseenCount, doSeeAllNotifications } = props;
|
||||
|
||||
const { push } = useHistory();
|
||||
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
|
||||
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS;
|
||||
|
||||
function handleMenuClick() {
|
||||
if (unseenCount > 0) doSeeAllNotifications();
|
||||
|
|
|
@ -1,18 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import { selectUserEmail, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import HeaderProfileMenuButton from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
email: selectUserEmail(state),
|
||||
authenticated: selectUserVerifiedEmail(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
openSignOutModal: () => dispatch(doOpenModal(MODALS.SIGN_OUT)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(HeaderProfileMenuButton);
|
||||
export default connect(select)(HeaderProfileMenuButton);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
||||
import { Menu, MenuList, MenuButton } from '@reach/menu-button';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
|
@ -11,13 +11,10 @@ import React from 'react';
|
|||
|
||||
type HeaderMenuButtonProps = {
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
email: ?string,
|
||||
authenticated: boolean,
|
||||
openSignOutModal: () => void,
|
||||
};
|
||||
|
||||
export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) {
|
||||
const { activeChannelClaim, email, openSignOutModal, authenticated } = props;
|
||||
const { activeChannelClaim } = props;
|
||||
|
||||
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
|
||||
|
||||
|
@ -43,8 +40,8 @@ export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) {
|
|||
<HeaderMenuLink page={PAGES.UPLOADS} icon={ICONS.PUBLISH} name={__('Uploads')} />
|
||||
<HeaderMenuLink page={PAGES.CHANNELS} icon={ICONS.CHANNEL} name={__('Channels')} />
|
||||
<HeaderMenuLink page={PAGES.CREATOR_DASHBOARD} icon={ICONS.ANALYTICS} name={__('Creator Analytics')} />
|
||||
|
||||
{authenticated ? (
|
||||
{/* No sync button for now
|
||||
{authenticated ? (
|
||||
<MenuItem onSelect={openSignOutModal}>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
||||
|
@ -53,8 +50,9 @@ export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) {
|
|||
<span className="menu__link-help">{email}</span>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<HeaderMenuLink page={PAGES.AUTH_SIGNIN} icon={ICONS.SIGN_IN} name={__('Cloud Connect')} />
|
||||
<HeaderMenuLink page={PAGES.AUTH_SIGNIN} icon={ICONS.SIGN_IN} name={__('Maybe Sync')} />
|
||||
)}
|
||||
*/}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
|
||||
import { selectUser } from 'redux/selectors/user';
|
||||
import NotificationHeaderButton from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
unseenCount: selectUnseenNotificationCount(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
export default connect(select)(NotificationHeaderButton);
|
||||
|
|
|
@ -6,12 +6,11 @@ import { ENABLE_UI_NOTIFICATIONS } from 'config';
|
|||
type Props = {
|
||||
unseenCount: number,
|
||||
inline: boolean,
|
||||
user: ?User,
|
||||
};
|
||||
|
||||
export default function NotificationHeaderButton(props: Props) {
|
||||
const { unseenCount, inline = false, user } = props;
|
||||
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
|
||||
const { unseenCount, inline = false } = props;
|
||||
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS;
|
||||
|
||||
if (unseenCount === 0 || !notificationsEnabled) {
|
||||
return null;
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUser } from 'redux/selectors/user';
|
||||
import NudgeFloating from './view';
|
||||
|
||||
const select = state => ({
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
export default connect(select)(NudgeFloating);
|
||||
export default NudgeFloating;
|
||||
|
|
|
@ -5,22 +5,20 @@ import usePersistedState from 'effects/use-persisted-state';
|
|||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
user: ?User,
|
||||
name: string,
|
||||
text: string,
|
||||
};
|
||||
|
||||
export default function NudgeFloating(props: Props) {
|
||||
const { user, name, text } = props;
|
||||
const { name, text } = props;
|
||||
const [showNudge, setShowNudge] = React.useState(false);
|
||||
const [nudgeAcknowledged, setNudgeAcknowledged] = usePersistedState(name, false);
|
||||
const emailVerified = user && user.has_verified_email;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!emailVerified && !nudgeAcknowledged) {
|
||||
if (!nudgeAcknowledged) {
|
||||
setShowNudge(true);
|
||||
}
|
||||
}, [emailVerified, nudgeAcknowledged]);
|
||||
}, [nudgeAcknowledged]);
|
||||
|
||||
return (
|
||||
showNudge && (
|
||||
|
|
|
@ -1,23 +1,11 @@
|
|||
import { DOMAIN } from 'config';
|
||||
import { connect } from 'react-redux';
|
||||
import { doSetDaemonSetting } from 'redux/actions/settings';
|
||||
import { doSignOut } from 'redux/actions/app';
|
||||
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { doAuthenticate } from 'redux/actions/user';
|
||||
import { version as appVersion } from 'package.json';
|
||||
|
||||
import PrivacyAgreement from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
authenticated: selectUserVerifiedEmail(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
setShareDataInternal: (share) => dispatch(doSetDaemonSetting(DAEMON_SETTINGS.SHARE_USAGE_DATA, share)),
|
||||
signOut: () => dispatch(doSignOut()),
|
||||
authenticateIfSharingData: () =>
|
||||
dispatch(doAuthenticate(appVersion, undefined, undefined, true, undefined, undefined, DOMAIN)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(PrivacyAgreement);
|
||||
export default connect(null, perform)(PrivacyAgreement);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @flow
|
||||
import React, { useState } from 'react';
|
||||
import Button from 'component/button';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import { FormField } from 'component/common/form-components/form-field';
|
||||
import { Form } from 'component/common/form-components/form';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
@ -14,13 +13,11 @@ const NONE = 'none';
|
|||
type Props = {
|
||||
signOut: () => void,
|
||||
setShareDataInternal: (boolean) => void,
|
||||
authenticated: boolean,
|
||||
authenticateIfSharingData: () => void,
|
||||
handleNextPage: () => void,
|
||||
};
|
||||
|
||||
function PrivacyAgreement(props: Props) {
|
||||
const { setShareDataInternal, authenticated, signOut, authenticateIfSharingData, handleNextPage } = props;
|
||||
const { setShareDataInternal, handleNextPage } = props;
|
||||
const [share, setShare] = useState(undefined); // preload
|
||||
|
||||
function handleSubmit() {
|
||||
|
@ -30,10 +27,6 @@ function PrivacyAgreement(props: Props) {
|
|||
setShareDataInternal(false);
|
||||
}
|
||||
|
||||
if (share === LIMITED) {
|
||||
authenticateIfSharingData();
|
||||
}
|
||||
|
||||
handleNextPage();
|
||||
}
|
||||
|
||||
|
@ -63,7 +56,6 @@ function PrivacyAgreement(props: Props) {
|
|||
onChange={(e) => setShare(LIMITED)}
|
||||
/>
|
||||
<FormField
|
||||
disabled={authenticated}
|
||||
name={'shareNot'}
|
||||
type="radio"
|
||||
checked={share === NONE}
|
||||
|
@ -77,19 +69,6 @@ function PrivacyAgreement(props: Props) {
|
|||
)}
|
||||
onChange={(e) => setShare(NONE)}
|
||||
/>
|
||||
{authenticated && (
|
||||
<div className="card--inline section--padded">
|
||||
<p className="help--inline">
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
signout_button: <Button button="link" label={__('Sign Out')} onClick={signOut} />,
|
||||
}}
|
||||
>
|
||||
You are signed in and sharing data with your cloud service provider. %signout_button%.
|
||||
</I18nMessage>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</fieldset>
|
||||
<div className={'card__actions'}>
|
||||
<Button button="primary" label={__(`Next`)} disabled={!share} type="submit" />
|
||||
|
|
|
@ -2,18 +2,13 @@ import { connect } from 'react-redux';
|
|||
import { selectPublishFormValues } from 'redux/selectors/publish';
|
||||
import { doUpdatePublishForm } from 'redux/actions/publish';
|
||||
import PublishAdditionalOptions from './view';
|
||||
import { selectUser, selectAccessToken } from 'redux/selectors/user';
|
||||
import { doFetchAccessToken } from 'redux/actions/user';
|
||||
|
||||
const select = (state) => ({
|
||||
...selectPublishFormValues(state),
|
||||
accessToken: selectAccessToken(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
updatePublishForm: (value) => dispatch(doUpdatePublishForm(value)),
|
||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(PublishAdditionalOptions);
|
||||
|
|
|
@ -10,14 +10,7 @@ import Card from 'component/common/card';
|
|||
import SUPPORTED_LANGUAGES from 'constants/supported_languages';
|
||||
import { sortLanguageMap } from 'util/default-languages';
|
||||
|
||||
// @if TARGET='app'
|
||||
// import ErrorText from 'component/common/error-text';
|
||||
// import { LbryFirst } from 'lbry-redux';
|
||||
// import { ipcRenderer } from 'electron';
|
||||
// @endif
|
||||
|
||||
type Props = {
|
||||
user: ?User,
|
||||
language: ?string,
|
||||
name: ?string,
|
||||
licenseType: ?string,
|
||||
|
@ -25,92 +18,16 @@ type Props = {
|
|||
licenseUrl: ?string,
|
||||
disabled: boolean,
|
||||
updatePublishForm: ({}) => void,
|
||||
useLBRYUploader: boolean,
|
||||
needsYTAuth: boolean,
|
||||
fetchAccessToken: () => void,
|
||||
accessToken: string,
|
||||
};
|
||||
|
||||
function PublishAdditionalOptions(props: Props) {
|
||||
const {
|
||||
language,
|
||||
name,
|
||||
licenseType,
|
||||
otherLicenseDescription,
|
||||
licenseUrl,
|
||||
updatePublishForm,
|
||||
// user,
|
||||
// useLBRYUploader,
|
||||
// needsYTAuth,
|
||||
// accessToken,
|
||||
// fetchAccessToken,
|
||||
} = props;
|
||||
const { language, name, licenseType, otherLicenseDescription, licenseUrl, updatePublishForm } = props;
|
||||
const [hideSection, setHideSection] = usePersistedState('publish-advanced-options', true);
|
||||
// const [hasLaunchedLbryFirst, setHasLaunchedLbryFirst] = React.useState(false);
|
||||
// const [ytError, setYtError] = React.useState(false);
|
||||
// const isLBRYFirstUser = user && user.lbry_first_approved;
|
||||
// const showLbryFirstCheckbox = !IS_WEB && isLBRYFirstUser && hasLaunchedLbryFirst;
|
||||
|
||||
function toggleHideSection() {
|
||||
setHideSection(!hideSection);
|
||||
}
|
||||
|
||||
// @if TARGET='app'
|
||||
// function signup() {
|
||||
// updatePublishForm({ ytSignupPending: true });
|
||||
// LbryFirst.ytSignup()
|
||||
// .then(response => {
|
||||
// updatePublishForm({ needsYTAuth: false, ytSignupPending: false });
|
||||
// })
|
||||
// .catch(error => {
|
||||
// updatePublishForm({ ytSignupPending: false });
|
||||
// setYtError(true);
|
||||
// console.error(error); // eslint-disable-line
|
||||
// });
|
||||
// }
|
||||
|
||||
// function unlink() {
|
||||
// setYtError(false);
|
||||
|
||||
// LbryFirst.remove()
|
||||
// .then(response => {
|
||||
// updatePublishForm({ needsYTAuth: true });
|
||||
// })
|
||||
// .catch(error => {
|
||||
// setYtError(true);
|
||||
// console.error(error); // eslint-disable-line
|
||||
// });
|
||||
// }
|
||||
|
||||
// React.useEffect(() => {
|
||||
// if (!accessToken) {
|
||||
// fetchAccessToken();
|
||||
// }
|
||||
// }, [accessToken, fetchAccessToken]);
|
||||
|
||||
// React.useEffect(() => {
|
||||
// if (isLBRYFirstUser && !hasLaunchedLbryFirst) {
|
||||
// ipcRenderer.send('launch-lbry-first');
|
||||
// ipcRenderer.on('lbry-first-launched', () => {
|
||||
// setHasLaunchedLbryFirst(true);
|
||||
// });
|
||||
// }
|
||||
// }, [isLBRYFirstUser, hasLaunchedLbryFirst, setHasLaunchedLbryFirst]);
|
||||
|
||||
// React.useEffect(() => {
|
||||
// if (useLBRYUploader && isLBRYFirstUser && hasLaunchedLbryFirst && accessToken) {
|
||||
// LbryFirst.hasYTAuth(accessToken)
|
||||
// .then(response => {
|
||||
// updatePublishForm({ needsYTAuth: !response.HasAuth });
|
||||
// })
|
||||
// .catch(error => {
|
||||
// setYtError(true);
|
||||
// console.error(error); // eslint-disable-line
|
||||
// });
|
||||
// }
|
||||
// }, [updatePublishForm, useLBRYUploader, isLBRYFirstUser, hasLaunchedLbryFirst, accessToken]);
|
||||
// @endif
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="card--enable-overflow"
|
||||
|
@ -118,41 +35,6 @@ function PublishAdditionalOptions(props: Props) {
|
|||
<React.Fragment>
|
||||
{!hideSection && (
|
||||
<div className={classnames({ 'card--disabled': !name })}>
|
||||
{/* @if TARGET='app' */}
|
||||
{/* {showLbryFirstCheckbox && (
|
||||
<div className="section">
|
||||
<>
|
||||
<FormField
|
||||
checked={useLBRYUploader}
|
||||
type="checkbox"
|
||||
name="use_lbry_uploader_checkbox"
|
||||
onChange={event => updatePublishForm({ useLBRYUploader: !useLBRYUploader })}
|
||||
label={
|
||||
<React.Fragment>
|
||||
{__('Automagically upload to your youtube channel.')}{' '}
|
||||
<Button button="link" href="https://lbry.com/faq/lbry-uploader" label={__('Learn More')} />
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
{useLBRYUploader && (
|
||||
<div className="section__actions">
|
||||
{needsYTAuth ? (
|
||||
<Button
|
||||
button="primary"
|
||||
onClick={signup}
|
||||
label={__('Log In With YouTube')}
|
||||
disabled={false}
|
||||
/>
|
||||
) : (
|
||||
<Button button="alt" onClick={unlink} label={__('Unlink YouTube Channel')} disabled={false} />
|
||||
)}
|
||||
{ytError && <ErrorText>{__('There was an error with LBRY first publishing.')}</ErrorText>}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
)} */}
|
||||
{/* @endif */}
|
||||
<div className="section">
|
||||
<PublishReleaseDate />
|
||||
|
||||
|
|
|
@ -17,12 +17,6 @@ import {
|
|||
} from 'redux/selectors/publish';
|
||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { doClaimInitialRewards } from 'redux/actions/rewards';
|
||||
import {
|
||||
selectUnclaimedRewardValue,
|
||||
selectIsClaimingInitialRewards,
|
||||
selectHasClaimedInitialRewards,
|
||||
} from 'redux/selectors/rewards';
|
||||
import {
|
||||
selectModal,
|
||||
selectActiveChannelClaim,
|
||||
|
@ -31,7 +25,6 @@ import {
|
|||
} from 'redux/selectors/app';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { makeSelectFileRenderModeForUri } from 'redux/selectors/content';
|
||||
import { selectUser } from 'redux/selectors/user';
|
||||
import PublishForm from './view';
|
||||
|
||||
const select = (state) => {
|
||||
|
@ -41,7 +34,6 @@ const select = (state) => {
|
|||
|
||||
return {
|
||||
...selectPublishFormValues(state),
|
||||
user: selectUser(state),
|
||||
// The winning claim for a short lbry uri
|
||||
amountNeededForTakeover: selectTakeOverAmount(state),
|
||||
isPostClaim,
|
||||
|
@ -55,14 +47,11 @@ const select = (state) => {
|
|||
remoteUrl: makeSelectPublishFormValue('remoteFileUrl')(state),
|
||||
publishSuccess: makeSelectPublishFormValue('publishSuccess')(state),
|
||||
isResolvingUri: selectIsResolvingPublishUris(state),
|
||||
totalRewardValue: selectUnclaimedRewardValue(state),
|
||||
modal: selectModal(state),
|
||||
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
incognito: selectIncognito(state),
|
||||
activeChannelStakedLevel: selectActiveChannelStakedLevel(state),
|
||||
isClaimingInitialRewards: selectIsClaimingInitialRewards(state),
|
||||
hasClaimedInitialRewards: selectHasClaimedInitialRewards(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -74,7 +63,6 @@ const perform = (dispatch) => ({
|
|||
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
||||
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
|
||||
checkAvailability: (name) => dispatch(doCheckPublishNameAvailability(name)),
|
||||
claimInitialRewards: () => dispatch(doClaimInitialRewards()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(PublishForm);
|
||||
|
|
|
@ -62,7 +62,6 @@ type Props = {
|
|||
licenseType: string,
|
||||
otherLicenseDescription: ?string,
|
||||
licenseUrl: ?string,
|
||||
useLBRYUploader: ?boolean,
|
||||
publishing: boolean,
|
||||
publishSuccess: boolean,
|
||||
balance: number,
|
||||
|
@ -76,19 +75,14 @@ type Props = {
|
|||
// Add back type
|
||||
updatePublishForm: (any) => void,
|
||||
checkAvailability: (string) => void,
|
||||
ytSignupPending: boolean,
|
||||
modal: { id: string, modalProps: {} },
|
||||
enablePublishPreview: boolean,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
incognito: boolean,
|
||||
user: ?User,
|
||||
activeChannelStakedLevel: number,
|
||||
isPostClaim: boolean,
|
||||
permanentUrl: ?string,
|
||||
remoteUrl: ?string,
|
||||
isClaimingInitialRewards: boolean,
|
||||
claimInitialRewards: () => void,
|
||||
hasClaimedInitialRewards: boolean,
|
||||
};
|
||||
|
||||
function PublishForm(props: Props) {
|
||||
|
@ -116,7 +110,6 @@ function PublishForm(props: Props) {
|
|||
publish,
|
||||
disabled = false,
|
||||
checkAvailability,
|
||||
ytSignupPending,
|
||||
modal,
|
||||
enablePublishPreview,
|
||||
activeChannelClaim,
|
||||
|
@ -124,9 +117,6 @@ function PublishForm(props: Props) {
|
|||
isPostClaim,
|
||||
permanentUrl,
|
||||
remoteUrl,
|
||||
isClaimingInitialRewards,
|
||||
claimInitialRewards,
|
||||
hasClaimedInitialRewards,
|
||||
} = props;
|
||||
|
||||
const inEditMode = Boolean(editingURI);
|
||||
|
@ -224,12 +214,6 @@ function PublishForm(props: Props) {
|
|||
|
||||
const [previewing, setPreviewing] = React.useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasClaimedInitialRewards) {
|
||||
claimInitialRewards();
|
||||
}
|
||||
}, [hasClaimedInitialRewards, claimInitialRewards]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!modal) {
|
||||
setTimeout(() => {
|
||||
|
@ -240,9 +224,7 @@ function PublishForm(props: Props) {
|
|||
|
||||
let submitLabel;
|
||||
|
||||
if (isClaimingInitialRewards) {
|
||||
submitLabel = __('Claiming credits...');
|
||||
} else if (publishing) {
|
||||
if (publishing) {
|
||||
if (isStillEditing) {
|
||||
submitLabel = __('Saving...');
|
||||
} else {
|
||||
|
@ -536,12 +518,7 @@ function PublishForm(props: Props) {
|
|||
onClick={handlePublish}
|
||||
label={submitLabel}
|
||||
disabled={
|
||||
isClaimingInitialRewards ||
|
||||
formDisabled ||
|
||||
!formValid ||
|
||||
uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS ||
|
||||
ytSignupPending ||
|
||||
previewing
|
||||
formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS || previewing
|
||||
}
|
||||
/>
|
||||
<Button button="link" onClick={clearPublish} label={__('New --[clears Publish Form]--')} />
|
||||
|
|
|
@ -6,7 +6,6 @@ import Card from 'component/common/card';
|
|||
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
|
||||
import Button from 'component/button';
|
||||
import classnames from 'classnames';
|
||||
import RecSys from 'recsys';
|
||||
|
||||
const VIEW_ALL_RELATED = 'view_all_related';
|
||||
const VIEW_MORE_FROM = 'view_more_from';
|
||||
|
@ -18,48 +17,20 @@ type Props = {
|
|||
isSearching: boolean,
|
||||
doFetchRecommendedContent: (string) => void,
|
||||
claim: ?StreamClaim,
|
||||
claimId: string,
|
||||
};
|
||||
|
||||
export default React.memo<Props>(function RecommendedContent(props: Props) {
|
||||
const {
|
||||
uri,
|
||||
doFetchRecommendedContent,
|
||||
recommendedContentUris,
|
||||
nextRecommendedUri,
|
||||
isSearching,
|
||||
claim,
|
||||
claimId,
|
||||
} = props;
|
||||
const { uri, doFetchRecommendedContent, recommendedContentUris, isSearching, claim } = props;
|
||||
const [viewMode, setViewMode] = React.useState(VIEW_ALL_RELATED);
|
||||
const signingChannel = claim && claim.signing_channel;
|
||||
const channelName = signingChannel ? signingChannel.name : null;
|
||||
const isMobile = useIsMobile();
|
||||
const isMedium = useIsMediumScreen();
|
||||
const { onRecsLoaded: onRecommendationsLoaded, onClickedRecommended: onRecommendationClicked } = RecSys;
|
||||
|
||||
React.useEffect(() => {
|
||||
doFetchRecommendedContent(uri);
|
||||
}, [uri, doFetchRecommendedContent]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Right now we only want to record the recs if they actually saw them.
|
||||
if (
|
||||
recommendedContentUris &&
|
||||
recommendedContentUris.length &&
|
||||
nextRecommendedUri &&
|
||||
viewMode === VIEW_ALL_RELATED
|
||||
) {
|
||||
onRecommendationsLoaded(claimId, recommendedContentUris);
|
||||
}
|
||||
}, [recommendedContentUris, onRecommendationsLoaded, claimId, nextRecommendedUri, viewMode]);
|
||||
|
||||
function handleRecommendationClicked(e, clickedClaim) {
|
||||
if (claim) {
|
||||
onRecommendationClicked(claim.claim_id, clickedClaim.claim_id);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
isBodyList
|
||||
|
@ -96,7 +67,6 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
|
|||
uris={recommendedContentUris}
|
||||
hideMenu={isMobile}
|
||||
empty={__('No related content found')}
|
||||
onClick={handleRecommendationClicked}
|
||||
/>
|
||||
)}
|
||||
{viewMode === VIEW_MORE_FROM && signingChannel && (
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectHasNavigated, selectScrollStartingPosition, selectWelcomeVersion } from 'redux/selectors/app';
|
||||
import { selectHomepageData } from 'redux/selectors/settings';
|
||||
import Router from './view';
|
||||
import { normalizeURI } from 'util/lbryURI';
|
||||
import { selectTitleForUri } from 'redux/selectors/claims';
|
||||
import { doSetHasNavigated } from 'redux/actions/app';
|
||||
import { doUserSetReferrer } from 'redux/actions/user';
|
||||
import { selectHasUnclaimedRefereeReward } from 'redux/selectors/rewards';
|
||||
|
||||
const select = (state) => {
|
||||
const { pathname, hash } = state.router.location;
|
||||
|
@ -30,17 +27,14 @@ const select = (state) => {
|
|||
uri,
|
||||
title: selectTitleForUri(state, uri),
|
||||
currentScroll: selectScrollStartingPosition(state),
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
welcomeVersion: selectWelcomeVersion(state),
|
||||
hasNavigated: selectHasNavigated(state),
|
||||
hasUnclaimedRefereeReward: selectHasUnclaimedRefereeReward(state),
|
||||
homepageData: selectHomepageData(state),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
setHasNavigated: () => dispatch(doSetHasNavigated()),
|
||||
setReferrer: (referrer) => dispatch(doUserSetReferrer(referrer)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(Router);
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
|
|||
import * as PAGES from 'constants/pages';
|
||||
import { PAGE_TITLE } from 'constants/pageTitles';
|
||||
import { LINKED_COMMENT_QUERY_PARAM } from 'constants/comment';
|
||||
import { parseURI, isURIValid } from 'util/lbryURI';
|
||||
import { parseURI } from 'util/lbryURI';
|
||||
import { WELCOME_VERSION } from 'config';
|
||||
import { GetLinksData } from 'util/buildHomepage';
|
||||
import { useIsLargeScreen } from 'effects/use-screensize';
|
||||
|
@ -14,14 +14,9 @@ import HomePage from 'page/home';
|
|||
import BackupPage from 'page/backup';
|
||||
|
||||
// Chunk: "secondary"
|
||||
import SignInPage from 'page/signIn';
|
||||
import SignInWalletPasswordPage from 'page/signInWalletPassword';
|
||||
|
||||
import SignUpPage from 'page/signUp';
|
||||
import SignInVerifyPage from 'page/signInVerify';
|
||||
|
||||
// Chunk: "wallet/secondary"
|
||||
import BuyPage from 'page/buy';
|
||||
import ReceivePage from 'page/receive';
|
||||
import SendPage from 'page/send';
|
||||
import WalletPage from 'page/wallet';
|
||||
|
@ -43,8 +38,6 @@ import ListBlockedPage from 'page/listBlocked';
|
|||
import ListsPage from 'page/lists';
|
||||
import PlaylistsPage from 'page/playlists';
|
||||
import OwnComments from 'page/ownComments';
|
||||
import PasswordResetPage from 'page/passwordReset';
|
||||
import PasswordSetPage from 'page/passwordSet';
|
||||
import PublishPage from 'page/publish';
|
||||
import ReportContentPage from 'page/reportContent';
|
||||
import ReportPage from 'page/report';
|
||||
|
@ -53,6 +46,7 @@ import SearchPage from 'page/search';
|
|||
|
||||
import SettingsCreatorPage from 'page/settingsCreator';
|
||||
import SettingsNotificationsPage from 'page/settingsNotifications';
|
||||
import SettingsSyncPage from 'page/settingsSync';
|
||||
|
||||
import SettingsPage from 'page/settings';
|
||||
import ShowPage from 'page/show';
|
||||
|
@ -70,7 +64,6 @@ if ('scrollRestoration' in history) {
|
|||
|
||||
type Props = {
|
||||
currentScroll: number,
|
||||
isAuthenticated: boolean,
|
||||
location: { pathname: string, search: string, hash: string },
|
||||
history: {
|
||||
action: string,
|
||||
|
@ -90,14 +83,12 @@ type Props = {
|
|||
welcomeVersion: number,
|
||||
hasNavigated: boolean,
|
||||
setHasNavigated: () => void,
|
||||
setReferrer: (?string) => void,
|
||||
hasUnclaimedRefereeReward: boolean,
|
||||
homepageData: any,
|
||||
};
|
||||
|
||||
type PrivateRouteProps = Props & {
|
||||
component: any,
|
||||
isAuthenticated: boolean,
|
||||
isAuthenticated?: boolean,
|
||||
};
|
||||
|
||||
function PrivateRoute(props: PrivateRouteProps) {
|
||||
|
@ -109,15 +100,12 @@ function AppRouter(props: Props) {
|
|||
const {
|
||||
currentScroll,
|
||||
location: { pathname, search, hash },
|
||||
isAuthenticated,
|
||||
history,
|
||||
uri,
|
||||
title,
|
||||
welcomeVersion,
|
||||
hasNavigated,
|
||||
setHasNavigated,
|
||||
hasUnclaimedRefereeReward,
|
||||
setReferrer,
|
||||
homepageData,
|
||||
} = props;
|
||||
const { entries, listen, action: historyAction } = history;
|
||||
|
@ -140,16 +128,6 @@ function AppRouter(props: Props) {
|
|||
return unlisten;
|
||||
}, [listen, hasNavigated, setHasNavigated]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasNavigated && hasUnclaimedRefereeReward && !isAuthenticated) {
|
||||
const valid = isURIValid(uri);
|
||||
if (valid) {
|
||||
const { path } = parseURI(uri);
|
||||
if (path !== 'undefined') setReferrer(path);
|
||||
}
|
||||
}
|
||||
}, [hasNavigated, uri, hasUnclaimedRefereeReward, setReferrer, isAuthenticated]);
|
||||
|
||||
useEffect(() => {
|
||||
const getDefaultTitle = (pathname: string) => {
|
||||
const title = pathname.startsWith('/$/') ? PAGE_TITLE[pathname.substring(3)] : '';
|
||||
|
@ -175,9 +153,7 @@ function AppRouter(props: Props) {
|
|||
document.title = getDefaultTitle(pathname);
|
||||
}
|
||||
|
||||
// @if TARGET='app'
|
||||
entries[entryIndex].title = document.title;
|
||||
// @endif
|
||||
}, [pathname, entries, entryIndex, title, uri]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -227,19 +203,10 @@ function AppRouter(props: Props) {
|
|||
))}
|
||||
|
||||
{/* Odysee signin */}
|
||||
<Route path={`/$/${PAGES.AUTH_SIGNIN}`} exact component={SignInPage} />
|
||||
<Route path={`/$/${PAGES.AUTH_PASSWORD_RESET}`} exact component={PasswordResetPage} />
|
||||
<Route path={`/$/${PAGES.AUTH_PASSWORD_SET}`} exact component={PasswordSetPage} />
|
||||
<Route path={`/$/${PAGES.AUTH}`} exact component={SignUpPage} />
|
||||
<Route path={`/$/${PAGES.AUTH}/*`} exact component={SignUpPage} />
|
||||
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
|
||||
|
||||
<Route path={`/$/${PAGES.WELCOME}`} exact component={Welcome} />
|
||||
|
||||
<Route path={`/$/${PAGES.SETTINGS_SYNC}`} exact component={SettingsSyncPage} />
|
||||
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
||||
{/* @if TARGET='app' */}
|
||||
<Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} />
|
||||
{/* @endif */}
|
||||
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
||||
<Route path={`/$/${PAGES.TOP}`} exact component={TopPage} />
|
||||
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
|
||||
|
@ -270,7 +237,6 @@ function AppRouter(props: Props) {
|
|||
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_CREATOR}`} component={SettingsCreatorPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.BUY}`} component={BuyPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.RECEIVE}`} component={ReceivePage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.SEND}`} component={SendPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.NOTIFICATIONS}`} component={NotificationsPage} />
|
||||
|
|
|
@ -3,7 +3,6 @@ import SelectChannel from './view';
|
|||
import { selectBalance } from 'redux/selectors/wallet';
|
||||
import { selectMyChannelClaims, selectFetchingMyChannels } from 'redux/selectors/claims';
|
||||
import { doFetchChannelListMine, doCreateChannel } from 'redux/actions/claims';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import { doSetActiveChannel } from 'redux/actions/app';
|
||||
|
||||
|
@ -11,7 +10,6 @@ const select = (state) => ({
|
|||
myChannelClaims: selectMyChannelClaims(state),
|
||||
fetchingChannels: selectFetchingMyChannels(state),
|
||||
balance: selectBalance(state),
|
||||
emailVerified: selectUserVerifiedEmail(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
});
|
||||
|
||||
|
|
|
@ -2,14 +2,11 @@ import { connect } from 'react-redux';
|
|||
import { selectHasChannels } from 'redux/selectors/claims';
|
||||
import { selectWalletIsEncrypted } from 'redux/selectors/wallet';
|
||||
import { doWalletStatus } from 'redux/actions/wallet';
|
||||
import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
|
||||
import SettingAccount from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
walletEncrypted: selectWalletIsEncrypted(state),
|
||||
user: selectUser(state),
|
||||
hasChannels: selectHasChannels(state),
|
||||
});
|
||||
|
||||
|
|
|
@ -6,27 +6,26 @@ import React from 'react';
|
|||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
import SettingsRow from 'component/settingsRow';
|
||||
import SyncToggle from 'component/syncToggle';
|
||||
// maybe bring this back
|
||||
// import SyncToggle from 'component/syncToggle';
|
||||
import { getPasswordFromCookie } from 'util/saved-passwords';
|
||||
|
||||
type Props = {
|
||||
isAuthenticated: boolean,
|
||||
walletEncrypted: boolean,
|
||||
user: User,
|
||||
hasChannels: boolean,
|
||||
doWalletStatus: () => void,
|
||||
};
|
||||
|
||||
export default function SettingAccount(props: Props) {
|
||||
const { isAuthenticated, walletEncrypted, hasChannels, doWalletStatus } = props;
|
||||
const [storedPassword, setStoredPassword] = React.useState(false);
|
||||
const { hasChannels, doWalletStatus } = props;
|
||||
// const [storedPassword, setStoredPassword] = React.useState(false);
|
||||
|
||||
// Determine if password is stored.
|
||||
React.useEffect(() => {
|
||||
doWalletStatus();
|
||||
getPasswordFromCookie().then((p) => {
|
||||
if (typeof p === 'string') {
|
||||
setStoredPassword(true);
|
||||
// get password
|
||||
}
|
||||
});
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
@ -42,18 +41,8 @@ export default function SettingAccount(props: Props) {
|
|||
isBodyList
|
||||
body={
|
||||
<>
|
||||
{isAuthenticated && (
|
||||
<SettingsRow title={__('Password')}>
|
||||
<Button
|
||||
button="inverse"
|
||||
label={__('Manage')}
|
||||
icon={ICONS.ARROW_RIGHT}
|
||||
navigate={`/$/${PAGES.SETTINGS_UPDATE_PWD}`}
|
||||
/>
|
||||
</SettingsRow>
|
||||
)}
|
||||
|
||||
<SyncToggle disabled={walletEncrypted && !storedPassword && storedPassword !== ''} />
|
||||
{/* This will probably start the new sync flow when checked (-> openModal(SYNC_ENABLE) ) */}
|
||||
{/* <SyncToggle disabled={true} /> */}
|
||||
|
||||
{hasChannels && (
|
||||
<SettingsRow title={__('Comments')} subtitle={__('View your past comments.')}>
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUser, selectPasswordSetSuccess, selectPasswordSetError } from 'redux/selectors/user';
|
||||
import { doUserPasswordSet, doClearPasswordEntry } from 'redux/actions/user';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import UserSignIn from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
user: selectUser(state),
|
||||
passwordSetSuccess: selectPasswordSetSuccess(state),
|
||||
passwordSetError: selectPasswordSetError(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doUserPasswordSet,
|
||||
doToast,
|
||||
doClearPasswordEntry,
|
||||
})(UserSignIn);
|
|
@ -1,81 +0,0 @@
|
|||
// @flow
|
||||
import React, { useState } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { FormField, Form } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import ErrorText from 'component/common/error-text';
|
||||
import SettingsRow from 'component/settingsRow';
|
||||
import * as PAGES from 'constants/pages';
|
||||
|
||||
type Props = {
|
||||
user: ?User,
|
||||
doToast: ({ message: string }) => void,
|
||||
doUserPasswordSet: (string, ?string) => void,
|
||||
doClearPasswordEntry: () => void,
|
||||
passwordSetSuccess: boolean,
|
||||
passwordSetError: ?string,
|
||||
};
|
||||
|
||||
export default function SettingAccountPassword(props: Props) {
|
||||
const { user, doToast, doUserPasswordSet, passwordSetSuccess, passwordSetError, doClearPasswordEntry } = props;
|
||||
const [oldPassword, setOldPassword] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const hasPassword = user && user.password_set;
|
||||
const { goBack } = useHistory();
|
||||
|
||||
const title = hasPassword ? __('Update Your Password') : __('Add A Password');
|
||||
const subtitle = hasPassword ? '' : __('You do not currently have a password set.');
|
||||
|
||||
function handleSubmit() {
|
||||
doUserPasswordSet(newPassword, oldPassword);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (passwordSetSuccess) {
|
||||
goBack();
|
||||
doToast({
|
||||
message: __('Password updated successfully.'),
|
||||
});
|
||||
doClearPasswordEntry();
|
||||
setOldPassword('');
|
||||
setNewPassword('');
|
||||
}
|
||||
}, [passwordSetSuccess, setOldPassword, setNewPassword, doClearPasswordEntry, doToast, goBack]);
|
||||
|
||||
return (
|
||||
<SettingsRow title={title} subtitle={subtitle} multirow>
|
||||
<Form onSubmit={handleSubmit} className="section">
|
||||
{hasPassword && (
|
||||
<FormField
|
||||
type="password"
|
||||
name="setting_set_old_password"
|
||||
label={__('Old Password')}
|
||||
value={oldPassword}
|
||||
onChange={(e) => setOldPassword(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
type="password"
|
||||
name="setting_set_new_password"
|
||||
label={__('New Password')}
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="section__actions">
|
||||
<Button button="primary" type="submit" label={__('Set Password')} disabled={!newPassword} />
|
||||
{hasPassword ? (
|
||||
<Button button="link" label={__('Forgot Password?')} navigate={`/$/${PAGES.AUTH_PASSWORD_RESET}`} />
|
||||
) : (
|
||||
<Button button="link" label={__('Cancel')} onClick={() => goBack()} />
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
{passwordSetError && (
|
||||
<div className="section">
|
||||
<ErrorText>{passwordSetError}</ErrorText>
|
||||
</div>
|
||||
)}
|
||||
</SettingsRow>
|
||||
);
|
||||
}
|
|
@ -4,12 +4,10 @@ import * as SETTINGS from 'constants/settings';
|
|||
import { doSetPlayingUri, clearContentCache } from 'redux/actions/content';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
|
||||
import SettingContent from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
floatingPlayer: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state),
|
||||
autoplayMedia: makeSelectClientSetting(SETTINGS.AUTOPLAY_MEDIA)(state),
|
||||
autoplayNext: makeSelectClientSetting(SETTINGS.AUTOPLAY_NEXT)(state),
|
||||
|
|
|
@ -3,7 +3,6 @@ import * as ICONS from 'constants/icons';
|
|||
import * as PAGES from 'constants/pages';
|
||||
import React from 'react';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import { SETTINGS_GRP } from 'constants/settings';
|
||||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
|
@ -18,7 +17,6 @@ type Price = {
|
|||
|
||||
type Props = {
|
||||
// --- select ---
|
||||
isAuthenticated: boolean,
|
||||
floatingPlayer: boolean,
|
||||
autoplayMedia: boolean,
|
||||
autoplayNext: boolean,
|
||||
|
@ -37,7 +35,6 @@ type Props = {
|
|||
|
||||
export default function SettingContent(props: Props) {
|
||||
const {
|
||||
isAuthenticated,
|
||||
floatingPlayer,
|
||||
autoplayMedia,
|
||||
autoplayNext,
|
||||
|
@ -110,10 +107,6 @@ export default function SettingContent(props: Props) {
|
|||
type="checkbox"
|
||||
name="hide_reposts"
|
||||
onChange={(e) => {
|
||||
if (isAuthenticated) {
|
||||
let param = e.target.checked ? { add: 'noreposts' } : { remove: 'noreposts' };
|
||||
Lbryio.call('user_tag', 'edit', param);
|
||||
}
|
||||
setClientSetting(SETTINGS.HIDE_REPOSTS, !hideReposts);
|
||||
}}
|
||||
checked={hideReposts}
|
||||
|
@ -172,7 +165,7 @@ export default function SettingContent(props: Props) {
|
|||
</SettingsRow>
|
||||
|
||||
{myChannelUrls && myChannelUrls.length > 0 && (
|
||||
<SettingsRow title={__('Creator settings')}>
|
||||
<SettingsRow title={__('Creator Comment settings')}>
|
||||
<Button
|
||||
button="inverse"
|
||||
label={__('Manage')}
|
||||
|
|
|
@ -6,12 +6,12 @@ import {
|
|||
doNotifyDecryptWallet,
|
||||
doNotifyEncryptWallet,
|
||||
doNotifyForgetPassword,
|
||||
doOpenModal,
|
||||
doToggle3PAnalytics,
|
||||
} from 'redux/actions/app';
|
||||
import { doSetDaemonSetting, doClearDaemonSetting, doFindFFmpeg } from 'redux/actions/settings';
|
||||
import { selectAllowAnalytics } from 'redux/selectors/app';
|
||||
import { selectDaemonSettings, selectFfmpegStatus, selectFindingFFmpeg } from 'redux/selectors/settings';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user'; // here
|
||||
|
||||
import SettingSystem from './view';
|
||||
|
||||
|
@ -20,7 +20,6 @@ const select = (state) => ({
|
|||
ffmpegStatus: selectFfmpegStatus(state),
|
||||
findingFFmpeg: selectFindingFFmpeg(state),
|
||||
walletEncrypted: selectWalletIsEncrypted(state),
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
allowAnalytics: selectAllowAnalytics(state),
|
||||
});
|
||||
|
||||
|
@ -34,6 +33,7 @@ const perform = (dispatch) => ({
|
|||
updateWalletStatus: () => dispatch(doWalletStatus()),
|
||||
confirmForgetPassword: (modalProps) => dispatch(doNotifyForgetPassword(modalProps)),
|
||||
toggle3PAnalytics: (allow) => dispatch(doToggle3PAnalytics(allow)),
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(SettingSystem);
|
||||
|
|
|
@ -18,6 +18,8 @@ import { getPasswordFromCookie } from 'util/saved-passwords';
|
|||
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
|
||||
import SettingEnablePrereleases from 'component/settingEnablePrereleases';
|
||||
import SettingDisableAutoUpdates from 'component/settingDisableAutoUpdates';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as PAGES from 'constants/pages';
|
||||
|
||||
const IS_MAC = process.platform === 'darwin';
|
||||
|
||||
|
@ -44,7 +46,6 @@ type Props = {
|
|||
ffmpegStatus: { available: boolean, which: string },
|
||||
findingFFmpeg: boolean,
|
||||
walletEncrypted: boolean,
|
||||
isAuthenticated: boolean,
|
||||
allowAnalytics: boolean,
|
||||
// --- perform ---
|
||||
setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
|
||||
|
@ -64,7 +65,6 @@ export default function SettingSystem(props: Props) {
|
|||
ffmpegStatus,
|
||||
findingFFmpeg,
|
||||
walletEncrypted,
|
||||
isAuthenticated,
|
||||
allowAnalytics,
|
||||
setDaemonSetting,
|
||||
clearDaemonSetting,
|
||||
|
@ -150,6 +150,14 @@ export default function SettingSystem(props: Props) {
|
|||
checked={daemonSettings.save_files}
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow title={__('Remote Sync Settings')}>
|
||||
<Button
|
||||
button="inverse"
|
||||
label={__('Manage')}
|
||||
icon={ICONS.ARROW_RIGHT}
|
||||
navigate={`/$/${PAGES.SETTINGS_SYNC}`}
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
title={__('Share usage and diagnostic data')}
|
||||
subtitle={
|
||||
|
@ -168,12 +176,7 @@ export default function SettingSystem(props: Props) {
|
|||
onChange={() => setDaemonSetting('share_usage_data', !daemonSettings.share_usage_data)}
|
||||
checked={daemonSettings.share_usage_data}
|
||||
label={<React.Fragment>{__('Allow the app to share data to LBRY.inc')}</React.Fragment>}
|
||||
helper={
|
||||
isAuthenticated
|
||||
? __('Internal sharing is required while signed in.')
|
||||
: __('Internal sharing is required to participate in rewards programs.')
|
||||
}
|
||||
disabled={isAuthenticated && daemonSettings.share_usage_data}
|
||||
helper={__('Internal sharing is required to participate in rewards programs.')}
|
||||
/>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
|
@ -276,24 +279,13 @@ export default function SettingSystem(props: Props) {
|
|||
title={__('Encrypt my wallet with a custom password')}
|
||||
subtitle={
|
||||
<React.Fragment>
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
learn_more: (
|
||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/account-sync" />
|
||||
),
|
||||
}}
|
||||
>
|
||||
Wallet encryption is currently unavailable until it's supported for synced accounts. It will be
|
||||
added back soon. %learn_more%.
|
||||
</I18nMessage>
|
||||
{/* {__('Secure your local wallet data with a custom password.')}{' '}
|
||||
<strong>{__('Lost passwords cannot be recovered.')} </strong>
|
||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />. */}
|
||||
{__('Secure your local wallet data with a custom password.')}{' '}
|
||||
<strong>{__('Lost passwords cannot be recovered.')} </strong>
|
||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />
|
||||
</React.Fragment>
|
||||
}
|
||||
>
|
||||
<FormField
|
||||
disabled
|
||||
type="checkbox"
|
||||
name="encrypt_wallet"
|
||||
onChange={() => onChangeEncryptWallet()}
|
||||
|
|
|
@ -2,7 +2,6 @@ import { connect } from 'react-redux';
|
|||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import { doClearPurchasedUriSuccess } from 'redux/actions/file';
|
||||
import { selectFollowedTags } from 'redux/selectors/tags';
|
||||
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
|
||||
import { selectHomepageData } from 'redux/selectors/settings';
|
||||
import { doSignOut } from 'redux/actions/app';
|
||||
import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
|
||||
|
@ -13,10 +12,8 @@ import SideNavigation from './view';
|
|||
const select = (state) => ({
|
||||
subscriptions: selectSubscriptions(state),
|
||||
followedTags: selectFollowedTags(state),
|
||||
email: selectUserVerifiedEmail(state),
|
||||
purchaseSuccess: selectPurchaseUriSuccess(state),
|
||||
unseenCount: selectUnseenNotificationCount(state),
|
||||
user: selectUser(state),
|
||||
homepageData: selectHomepageData(state),
|
||||
});
|
||||
|
||||
|
|
|
@ -6,13 +6,11 @@ import * as KEYCODES from 'constants/keycodes';
|
|||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import classnames from 'classnames';
|
||||
import NotificationBubble from 'component/notificationBubble';
|
||||
import DebouncedInput from 'component/common/debounced-input';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import { useIsMobile, isTouch } from 'effects/use-screensize';
|
||||
import { IS_MAC } from 'component/app/view';
|
||||
import { useHistory } from 'react-router';
|
||||
import { ENABLE_UI_NOTIFICATIONS } from 'config';
|
||||
|
||||
const FOLLOWED_ITEM_INITIAL_LIMIT = 10;
|
||||
const touch = isTouch();
|
||||
|
@ -32,7 +30,6 @@ type SideNavLink = {
|
|||
type Props = {
|
||||
subscriptions: Array<Subscription>,
|
||||
followedTags: Array<Tag>,
|
||||
email: ?string,
|
||||
uploadCount: number,
|
||||
doSignOut: () => void,
|
||||
sidebarOpen: boolean,
|
||||
|
@ -42,7 +39,6 @@ type Props = {
|
|||
unseenCount: number,
|
||||
purchaseSuccess: boolean,
|
||||
doClearPurchasedUriSuccess: () => void,
|
||||
user: ?User,
|
||||
homepageData: any,
|
||||
activeChannelStakedLevel: number,
|
||||
};
|
||||
|
@ -51,7 +47,6 @@ function SideNavigation(props: Props) {
|
|||
const {
|
||||
subscriptions,
|
||||
doSignOut,
|
||||
email,
|
||||
purchaseSuccess,
|
||||
doClearPurchasedUriSuccess,
|
||||
sidebarOpen,
|
||||
|
@ -59,7 +54,6 @@ function SideNavigation(props: Props) {
|
|||
isMediumScreen,
|
||||
isOnFilePage,
|
||||
unseenCount,
|
||||
user,
|
||||
followedTags,
|
||||
} = props;
|
||||
|
||||
|
@ -99,13 +93,6 @@ function SideNavigation(props: Props) {
|
|||
icon: ICONS.PURCHASED,
|
||||
};
|
||||
|
||||
const NOTIFICATIONS = {
|
||||
title: 'Notifications',
|
||||
link: `/$/${PAGES.NOTIFICATIONS}`,
|
||||
icon: ICONS.NOTIFICATION,
|
||||
extra: <NotificationBubble inline />,
|
||||
};
|
||||
|
||||
const PLAYLISTS = {
|
||||
title: 'Lists',
|
||||
link: `/$/${PAGES.LISTS}`,
|
||||
|
@ -181,8 +168,6 @@ function SideNavigation(props: Props) {
|
|||
},
|
||||
];
|
||||
|
||||
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
|
||||
|
||||
const [pulseLibrary, setPulseLibrary] = React.useState(false);
|
||||
const [expandSubscriptions, setExpandSubscriptions] = React.useState(false);
|
||||
const [expandTags, setExpandTags] = React.useState(false);
|
||||
|
@ -241,10 +226,6 @@ function SideNavigation(props: Props) {
|
|||
const { hideForUnauth, route, link, ...passedProps } = props;
|
||||
const { title, icon, extra } = passedProps;
|
||||
|
||||
if (hideForUnauth && !email) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={route || link || title}>
|
||||
<Button
|
||||
|
@ -388,15 +369,11 @@ function SideNavigation(props: Props) {
|
|||
'navigation--push': showPushMenu,
|
||||
'navigation-file-page-and-mobile': hideMenuFromView,
|
||||
'navigation-touch': touch,
|
||||
// @if TARGET='app'
|
||||
'navigation--mac': IS_MAC,
|
||||
// @endif
|
||||
})}
|
||||
>
|
||||
{(!canDisposeMenu || sidebarOpen) && (
|
||||
<div className="navigation-inner-container">
|
||||
<ul className="navigation-links--absolute mobile-only">{notificationsEnabled && getLink(NOTIFICATIONS)}</ul>
|
||||
|
||||
<ul
|
||||
className={classnames('navigation-links', {
|
||||
'navigation-links--micro': showMicroMenu,
|
||||
|
@ -412,7 +389,7 @@ function SideNavigation(props: Props) {
|
|||
{getLink(PLAYLISTS)}
|
||||
</ul>
|
||||
<ul className="navigation-links--absolute mobile-only">
|
||||
{email && MOBILE_LINKS.map((linkProps) => getLink(linkProps))}
|
||||
{MOBILE_LINKS.map((linkProps) => getLink(linkProps))}
|
||||
</ul>
|
||||
|
||||
{getSubscriptionSection()}
|
||||
|
|
|
@ -63,8 +63,6 @@ function SocialShare(props: Props) {
|
|||
const shareUrl: string = generateShareUrl(
|
||||
shareDomain,
|
||||
lbryUrl,
|
||||
null,
|
||||
null,
|
||||
includeStartTime,
|
||||
startTimeSeconds,
|
||||
includedCollectionId
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
makeSelectNotificationsDisabled,
|
||||
} from 'redux/selectors/subscriptions';
|
||||
import { makeSelectPermanentUrlForUri } from 'redux/selectors/claims';
|
||||
import { selectUser } from 'redux/selectors/user';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import SubscribeButton from './view';
|
||||
|
||||
|
@ -15,7 +14,6 @@ const select = (state, props) => ({
|
|||
firstRunCompleted: selectFirstRunCompleted(state),
|
||||
permanentUrl: makeSelectPermanentUrlForUri(props.uri)(state),
|
||||
notificationsDisabled: makeSelectNotificationsDisabled(props.uri)(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
|
|
|
@ -21,7 +21,6 @@ type Props = {
|
|||
doToast: ({ message: string }) => void,
|
||||
shrinkOnMobile: boolean,
|
||||
notificationsDisabled: boolean,
|
||||
user: ?User,
|
||||
uri: string,
|
||||
};
|
||||
|
||||
|
@ -34,7 +33,6 @@ export default function SubscribeButton(props: Props) {
|
|||
doToast,
|
||||
shrinkOnMobile = false,
|
||||
notificationsDisabled,
|
||||
user,
|
||||
uri,
|
||||
} = props;
|
||||
|
||||
|
@ -42,7 +40,7 @@ export default function SubscribeButton(props: Props) {
|
|||
const isMobile = useIsMobile();
|
||||
let isHovering = useHover(buttonRef);
|
||||
isHovering = isMobile ? true : isHovering;
|
||||
const uiNotificationsEnabled = (user && user.experimental_ui) || ENABLE_UI_NOTIFICATIONS;
|
||||
const uiNotificationsEnabled = ENABLE_UI_NOTIFICATIONS;
|
||||
|
||||
const { channelName: rawChannelName } = parseURI(uri);
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import {
|
||||
selectGetSyncErrorMessage,
|
||||
selectHasSyncedWallet,
|
||||
|
@ -17,7 +16,6 @@ const select = (state) => ({
|
|||
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
|
||||
hasSyncedWallet: selectHasSyncedWallet(state),
|
||||
hasSyncChanged: selectHashChanged(state),
|
||||
verifiedEmail: selectUserVerifiedEmail(state),
|
||||
getSyncError: selectGetSyncErrorMessage(state),
|
||||
getSyncPending: selectGetSyncIsPending(state),
|
||||
});
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectGetSyncIsPending, selectSyncApplyPasswordError } from 'redux/selectors/sync';
|
||||
import { doGetSyncDesktop } from 'redux/actions/sync';
|
||||
import { selectUserEmail } from 'redux/selectors/user';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { doSignOut, doHandleSyncComplete } from 'redux/actions/app';
|
||||
import SyncPassword from './view';
|
||||
|
||||
const select = state => ({
|
||||
const select = (state) => ({
|
||||
getSyncIsPending: selectGetSyncIsPending(state),
|
||||
email: selectUserEmail(state),
|
||||
passwordError: selectSyncApplyPasswordError(state),
|
||||
// bring email in from new sync system
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
const perform = (dispatch) => ({
|
||||
getSync: (cb, password) => dispatch(doGetSyncDesktop(cb, password)),
|
||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||
handleSyncComplete: (error, hasDataChanged) => dispatch(doHandleSyncComplete(error, hasDataChanged)),
|
||||
|
|
|
@ -12,14 +12,14 @@ import { SITE_HELP_EMAIL } from 'config';
|
|||
type Props = {
|
||||
getSync: ((any, boolean) => void, ?string) => void,
|
||||
getSyncIsPending: boolean,
|
||||
email: string,
|
||||
passwordError: boolean,
|
||||
signOut: () => void,
|
||||
handleSyncComplete: (any, boolean) => void,
|
||||
email: string,
|
||||
};
|
||||
|
||||
function SyncPassword(props: Props) {
|
||||
const { getSync, getSyncIsPending, email, signOut, passwordError, handleSyncComplete } = props;
|
||||
const { getSync, getSyncIsPending, signOut, passwordError, handleSyncComplete, email = 'dummy' } = props;
|
||||
const {
|
||||
push,
|
||||
location: { search },
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doSetWalletSyncPreference } from 'redux/actions/settings';
|
||||
|
@ -9,7 +8,6 @@ import SyncToggle from './view';
|
|||
|
||||
const select = (state) => ({
|
||||
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
|
||||
verifiedEmail: selectUserVerifiedEmail(state),
|
||||
getSyncError: selectGetSyncErrorMessage(state),
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @flow
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import SettingsRow from 'component/settingsRow';
|
||||
import { withRouter } from 'react-router';
|
||||
import { FormField } from 'component/common/form';
|
||||
|
@ -9,7 +8,6 @@ import { FormField } from 'component/common/form';
|
|||
type Props = {
|
||||
setSyncEnabled: (boolean) => void,
|
||||
syncEnabled: boolean,
|
||||
verifiedEmail: ?string,
|
||||
history: { push: (string) => void },
|
||||
location: UrlLocation,
|
||||
getSyncError: ?string,
|
||||
|
@ -18,32 +16,24 @@ type Props = {
|
|||
};
|
||||
|
||||
function SyncToggle(props: Props) {
|
||||
const { verifiedEmail, openModal, syncEnabled, disabled } = props;
|
||||
// Redesign for new sync system
|
||||
const { openModal, syncEnabled, disabled } = props;
|
||||
|
||||
return (
|
||||
<SettingsRow
|
||||
title={__('Sync')}
|
||||
subtitle={disabled || !verifiedEmail ? '' : __('Sync your balance and preferences across devices.')}
|
||||
>
|
||||
<SettingsRow title={__('Sync')} subtitle={disabled ? '' : __('Sync your balance and preferences across devices.')}>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="sync_toggle"
|
||||
label={disabled || !verifiedEmail ? __('Sync your balance and preferences across devices.') : undefined}
|
||||
checked={syncEnabled && verifiedEmail}
|
||||
label={disabled ? __('Sync your balance and preferences across devices.') : undefined}
|
||||
checked={syncEnabled}
|
||||
onChange={() => openModal(MODALS.SYNC_ENABLE, { mode: syncEnabled ? 'disable' : 'enable' })}
|
||||
disabled={disabled || !verifiedEmail}
|
||||
disabled={disabled}
|
||||
helper={
|
||||
disabled
|
||||
? __("To enable Sync, close LBRY completely and check 'Remember Password' during wallet unlock.")
|
||||
: null
|
||||
}
|
||||
/>
|
||||
{!verifiedEmail && (
|
||||
<div>
|
||||
<p className="help">{__('An email address is required to sync your account.')}</p>
|
||||
<Button button="primary" label={__('Add Email')} />
|
||||
</div>
|
||||
)}
|
||||
</SettingsRow>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUnfollowedTags, selectFollowedTags } from 'redux/selectors/tags';
|
||||
import { doToggleTagFollowDesktop, doAddTag, doDeleteTag } from 'redux/actions/tags';
|
||||
import { selectUser } from 'redux/selectors/user';
|
||||
import DiscoveryFirstRun from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
unfollowedTags: props.unfollowedTags || selectUnfollowedTags(state),
|
||||
followedTags: props.followedTags || selectFollowedTags(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
|
|
|
@ -26,7 +26,6 @@ type Props = {
|
|||
disabled?: boolean,
|
||||
limitSelect?: number,
|
||||
limitShow?: number,
|
||||
user: User,
|
||||
disableControlTags?: boolean,
|
||||
};
|
||||
|
||||
|
@ -60,7 +59,6 @@ export default function TagsSearch(props: Props) {
|
|||
disabled,
|
||||
limitSelect = TAG_FOLLOW_MAX,
|
||||
limitShow = 5,
|
||||
user,
|
||||
disableControlTags,
|
||||
} = props;
|
||||
const [newTag, setNewTag] = useState('');
|
||||
|
@ -71,7 +69,7 @@ export default function TagsSearch(props: Props) {
|
|||
|
||||
// Make sure there are no duplicates, then trim
|
||||
// suggestedTags = (followedTags - tagsPassedIn) + unfollowedTags
|
||||
const experimentalFeature = user && user.experimental_ui;
|
||||
const experimentalFeature = false;
|
||||
const followedTagsSet = new Set(followedTags.map((tag) => tag.name));
|
||||
const selectedTagsSet = new Set(tagsPassedIn.map((tag) => tag.name));
|
||||
const unfollowedTagsSet = new Set(unfollowedTags.map((tag) => tag.name));
|
||||
|
@ -88,7 +86,6 @@ export default function TagsSearch(props: Props) {
|
|||
}
|
||||
});
|
||||
|
||||
// const countWithoutLbryFirst = selectedTagsSet.has('lbry-first') ? selectedTagsSet.size - 1 : selectedTagsSet.size;
|
||||
const maxed = Boolean(limitSelect && countWithoutSpecialTags >= limitSelect);
|
||||
const suggestedTags = Array.from(suggestedTagsSet).filter(doesTagMatch).slice(0, limitShow);
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectClaimedRewardsByTransactionId } from 'redux/selectors/rewards';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { selectIsFetchingTxos } from 'redux/selectors/wallet';
|
||||
import TransactionListTable from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
rewards: selectClaimedRewardsByTransactionId(state),
|
||||
loading: selectIsFetchingTxos(state),
|
||||
});
|
||||
|
||||
|
|
|
@ -9,12 +9,11 @@ type Props = {
|
|||
emptyMessage: ?string,
|
||||
loading: boolean,
|
||||
openModal: (id: string, { tx: Txo, cb: (string) => void }) => void,
|
||||
rewards: {},
|
||||
txos: Array<Txo>,
|
||||
};
|
||||
|
||||
function TransactionListTable(props: Props) {
|
||||
const { emptyMessage, rewards, loading, txos } = props;
|
||||
const { emptyMessage, loading, txos } = props;
|
||||
const REVOCABLE_TYPES = ['channel', 'stream', 'repost', 'support', 'claim', 'collection'];
|
||||
function revokeClaim(tx: any, cb: (string) => void) {
|
||||
props.openModal(MODALS.CONFIRM_CLAIM_REVOKE, { tx, cb });
|
||||
|
@ -48,7 +47,6 @@ function TransactionListTable(props: Props) {
|
|||
<TxoListItem
|
||||
key={`${t.txid}:${t.nout}-${i}`}
|
||||
txo={t}
|
||||
reward={rewards && rewards[t.txid]}
|
||||
isRevokeable={t.is_my_output && !t.is_spent && REVOCABLE_TYPES.includes(t.type)}
|
||||
revokeClaim={revokeClaim}
|
||||
/>
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doUserResendVerificationEmail, doUserCheckEmailVerified, doFetchAccessToken } from 'redux/actions/user';
|
||||
import { selectEmailToVerify, selectUser, selectAccessToken } from 'redux/selectors/user';
|
||||
import UserEmailVerify from './view';
|
||||
|
||||
const select = state => ({
|
||||
email: selectEmailToVerify(state),
|
||||
user: selectUser(state),
|
||||
accessToken: selectAccessToken(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
|
||||
checkEmailVerified: () => dispatch(doUserCheckEmailVerified()),
|
||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(UserEmailVerify);
|
|
@ -1,73 +0,0 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import type { Node } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import Button from 'component/button';
|
||||
import { FormField } from 'component/common/form';
|
||||
import UserSignOutButton from 'component/userSignOutButton';
|
||||
import Card from 'component/common/card';
|
||||
|
||||
type Props = {
|
||||
cancelButton: Node,
|
||||
email: string,
|
||||
resendVerificationEmail: (string) => void,
|
||||
checkEmailVerified: () => void,
|
||||
user: {
|
||||
has_verified_email: boolean,
|
||||
},
|
||||
fetchAccessToken: () => void,
|
||||
accessToken: string,
|
||||
};
|
||||
|
||||
function UserEmail(props: Props) {
|
||||
const { email, user, accessToken, fetchAccessToken } = props;
|
||||
|
||||
let isVerified = false;
|
||||
if (user) {
|
||||
isVerified = user.has_verified_email;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!accessToken) {
|
||||
fetchAccessToken();
|
||||
}
|
||||
}, [accessToken, fetchAccessToken]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={__('Cloud Account')}
|
||||
subtitle={
|
||||
isVerified
|
||||
? undefined
|
||||
: __(
|
||||
'Connecting with a cloud account will allow you to earn rewards, receive content and security updates, and optionally backup your data.'
|
||||
)
|
||||
}
|
||||
actions={
|
||||
isVerified ? (
|
||||
<FormField
|
||||
type="text"
|
||||
className="form-field--copyable"
|
||||
readOnly
|
||||
label={
|
||||
<React.Fragment>
|
||||
{__('Your email')}{' '}
|
||||
<Button
|
||||
button="link"
|
||||
label={__('Update mailing preferences')}
|
||||
href={`http://lbry.io/list/edit/${accessToken}`}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
inputButton={<UserSignOutButton button="secondary" />}
|
||||
value={email || ''}
|
||||
/>
|
||||
) : (
|
||||
<Button button="primary" label={__('Log In')} navigate={`/$/${PAGES.AUTH}`} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserEmail;
|
|
@ -1,11 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doClearEmailEntry, doUserSignUp } from 'redux/actions/user';
|
||||
import {
|
||||
selectEmailNewIsPending,
|
||||
selectEmailNewErrorMessage,
|
||||
selectEmailAlreadyExists,
|
||||
selectUser,
|
||||
} from 'redux/selectors/user';
|
||||
// new sync stuff
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
|
||||
import { doSetWalletSyncPreference, doSetDaemonSetting } from 'redux/actions/settings';
|
||||
|
@ -13,20 +7,14 @@ import { selectDaemonSettings, makeSelectClientSetting } from 'redux/selectors/s
|
|||
import UserEmailNew from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
isPending: selectEmailNewIsPending(state),
|
||||
errorMessage: selectEmailNewErrorMessage(state),
|
||||
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
|
||||
daemonSettings: selectDaemonSettings(state),
|
||||
emailExists: selectEmailAlreadyExists(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
setSync: (value) => dispatch(doSetWalletSyncPreference(value)),
|
||||
setShareDiagnosticData: (shouldShareData) =>
|
||||
dispatch(doSetDaemonSetting(DAEMON_SETTINGS.SHARE_USAGE_DATA, shouldShareData)),
|
||||
doSignUp: (email, password) => dispatch(doUserSignUp(email, password)),
|
||||
clearEmailEntry: () => dispatch(doClearEmailEntry()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(UserEmailNew);
|
||||
|
|
|
@ -1,102 +1,55 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import { DOMAIN } from 'config';
|
||||
import React, { useState } from 'react';
|
||||
/*
|
||||
Saving this component for sign in/up
|
||||
*/
|
||||
import React from 'react';
|
||||
import { FormField, Form } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import analytics from 'analytics';
|
||||
import { EMAIL_REGEX } from 'constants/email';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Card from 'component/common/card';
|
||||
import ErrorText from 'component/common/error-text';
|
||||
import Nag from 'component/common/nag';
|
||||
import classnames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
errorMessage: ?string,
|
||||
emailExists: boolean,
|
||||
isPending: boolean,
|
||||
// new sync stuff
|
||||
syncEnabled: boolean,
|
||||
setSync: (boolean) => void,
|
||||
balance: number,
|
||||
daemonSettings: { share_usage_data: boolean },
|
||||
setShareDiagnosticData: (boolean) => void,
|
||||
doSignUp: (string, ?string) => Promise<any>,
|
||||
clearEmailEntry: () => void,
|
||||
interestedInYoutubSync: boolean,
|
||||
doToggleInterestedInYoutubeSync: () => void,
|
||||
};
|
||||
|
||||
const SIGN_UP_MODE = 'signUp';
|
||||
const SIGN_IN_MODE = 'signIn';
|
||||
|
||||
function UserEmailNew(props: Props) {
|
||||
const {
|
||||
errorMessage,
|
||||
isPending,
|
||||
doSignUp,
|
||||
setSync,
|
||||
daemonSettings,
|
||||
setShareDiagnosticData,
|
||||
clearEmailEntry,
|
||||
emailExists,
|
||||
} = props;
|
||||
const { share_usage_data: shareUsageData } = daemonSettings;
|
||||
const { push, location } = useHistory();
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
const emailFromUrl = urlParams.get('email');
|
||||
const defaultEmail = emailFromUrl ? decodeURIComponent(emailFromUrl) : '';
|
||||
const [email, setEmail] = useState(defaultEmail);
|
||||
const [password, setPassword] = useState('');
|
||||
const [localShareUsageData, setLocalShareUsageData] = React.useState(false);
|
||||
const [formSyncEnabled, setFormSyncEnabled] = useState(true);
|
||||
const valid = email.match(EMAIL_REGEX);
|
||||
const [email, setEmail] = React.useState();
|
||||
const [password, setPassword] = React.useState();
|
||||
// const [server, setServer] = React.useState();
|
||||
// const [errormessage, setErrorMessage] = React.useState();
|
||||
const [mode, setMode] = React.useState(SIGN_UP_MODE);
|
||||
|
||||
function handleUsageDataChange() {
|
||||
setLocalShareUsageData(!localShareUsageData);
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
// @if TARGET='app'
|
||||
setSync(formSyncEnabled);
|
||||
setShareDiagnosticData(true);
|
||||
// @endif
|
||||
doSignUp(email, password === '' ? undefined : password).then(() => {
|
||||
analytics.emailProvidedEvent();
|
||||
});
|
||||
}
|
||||
|
||||
function handleChangeToSignIn(additionalParams) {
|
||||
clearEmailEntry();
|
||||
|
||||
let url = `/$/${PAGES.AUTH_SIGNIN}`;
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
|
||||
urlParams.delete('email');
|
||||
if (email) {
|
||||
urlParams.set('email', encodeURIComponent(email));
|
||||
}
|
||||
|
||||
urlParams.delete('email_exists');
|
||||
if (emailExists) {
|
||||
urlParams.set('email_exists', '1');
|
||||
}
|
||||
|
||||
push(`${url}?${urlParams.toString()}`);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (emailExists) {
|
||||
handleChangeToSignIn();
|
||||
}
|
||||
}, [emailExists]);
|
||||
// const shareUsageData = false;
|
||||
|
||||
const handleSubmit = () => {};
|
||||
return (
|
||||
<div className={classnames('main__sign-up')}>
|
||||
<Card
|
||||
title={__('Cloud Connect')}
|
||||
subtitle={__('Connect your wallet to Odysee')}
|
||||
actions={
|
||||
<div className={classnames({ 'card--disabled': DOMAIN === 'lbry.tv' && IS_WEB })}>
|
||||
<div>
|
||||
<Form onSubmit={handleSubmit} className="section">
|
||||
<FormField
|
||||
autoFocus
|
||||
placeholder={__('yourstruly@example.com')}
|
||||
type="email"
|
||||
name="sign_up_email"
|
||||
label={__('Email')}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<FormField
|
||||
autoFocus
|
||||
placeholder={__('yourstruly@example.com')}
|
||||
|
@ -113,45 +66,13 @@ function UserEmailNew(props: Props) {
|
|||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="sync_checkbox"
|
||||
label={
|
||||
<React.Fragment>
|
||||
{__('Backup your account and wallet data.')}{' '}
|
||||
<Button button="link" href="https://lbry.com/faq/account-sync" label={__('Learn More')} />
|
||||
</React.Fragment>
|
||||
}
|
||||
checked={formSyncEnabled}
|
||||
onChange={() => setFormSyncEnabled(!formSyncEnabled)}
|
||||
/>
|
||||
|
||||
{!shareUsageData && !IS_WEB && (
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="share_data_checkbox"
|
||||
checked={localShareUsageData}
|
||||
onChange={handleUsageDataChange}
|
||||
label={
|
||||
<React.Fragment>
|
||||
{__('Share usage data with LBRY inc.')}{' '}
|
||||
<Button button="link" href="https://lbry.com/faq/privacy-and-data" label={__('Learn More')} />
|
||||
{!localShareUsageData && <span className="error__text"> ({__('Required')})</span>}
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<div className="section__actions">
|
||||
<Button button="primary" type="submit" label={__('Sign Up')} disabled={!email || !password} />
|
||||
<Button
|
||||
button="primary"
|
||||
type="submit"
|
||||
label={__('Sign Up')}
|
||||
disabled={
|
||||
!email || !password || !valid || (!IS_WEB && !localShareUsageData && !shareUsageData) || isPending
|
||||
}
|
||||
button="link"
|
||||
onClick={setMode(mode === SIGN_UP_MODE ? SIGN_IN_MODE : SIGN_UP_MODE)}
|
||||
label={__('Log In')}
|
||||
/>
|
||||
<Button button="link" onClick={handleChangeToSignIn} label={__('Log In')} />
|
||||
</div>
|
||||
<p className="help--card-actions">
|
||||
<I18nMessage
|
||||
|
@ -165,7 +86,7 @@ function UserEmailNew(props: Props) {
|
|||
</Form>
|
||||
</div>
|
||||
}
|
||||
nag={<>{errorMessage && <Nag type="error" relative message={<ErrorText>{errorMessage}</ErrorText>} />}</>}
|
||||
nag={<>{'someMessage' && <Nag type="error" relative message={<ErrorText>{'someMessage'}</ErrorText>} />}</>}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectEmailNewErrorMessage,
|
||||
selectEmailToVerify,
|
||||
selectEmailDoesNotExist,
|
||||
selectEmailAlreadyExists,
|
||||
selectUser,
|
||||
selectEmailNewIsPending,
|
||||
} from 'redux/selectors/user';
|
||||
import { doUserCheckIfEmailExists, doClearEmailEntry } from 'redux/actions/user';
|
||||
import { doSetWalletSyncPreference } from 'redux/actions/settings';
|
||||
import UserEmailReturning from './view';
|
||||
|
||||
const select = state => ({
|
||||
errorMessage: selectEmailNewErrorMessage(state),
|
||||
emailToVerify: selectEmailToVerify(state),
|
||||
emailDoesNotExist: selectEmailDoesNotExist(state),
|
||||
emailExists: selectEmailAlreadyExists(state),
|
||||
isPending: selectEmailNewIsPending(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doUserCheckIfEmailExists,
|
||||
doClearEmailEntry,
|
||||
doSetWalletSyncPreference,
|
||||
})(UserEmailReturning);
|
|
@ -1,147 +0,0 @@
|
|||
// @flow
|
||||
import { CLOUD_CONNECT_SITE_NAME } from 'config';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React, { useState } from 'react';
|
||||
import { FormField, Form } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import { EMAIL_REGEX } from 'constants/email';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import UserEmailVerify from 'component/userEmailVerify';
|
||||
import Card from 'component/common/card';
|
||||
import Nag from 'component/common/nag';
|
||||
import classnames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
user: ?User,
|
||||
errorMessage: ?string,
|
||||
emailToVerify: ?string,
|
||||
emailDoesNotExist: boolean,
|
||||
doClearEmailEntry: () => void,
|
||||
doUserSignIn: (string, ?string) => void,
|
||||
doUserCheckIfEmailExists: (string) => void,
|
||||
doSetWalletSyncPreference: (boolean) => void,
|
||||
doSetClientSetting: (string, boolean, ?boolean) => void,
|
||||
isPending: boolean,
|
||||
};
|
||||
|
||||
function UserEmailReturning(props: Props) {
|
||||
const {
|
||||
user,
|
||||
errorMessage,
|
||||
doUserCheckIfEmailExists,
|
||||
emailToVerify,
|
||||
doClearEmailEntry,
|
||||
emailDoesNotExist,
|
||||
doSetWalletSyncPreference,
|
||||
isPending,
|
||||
} = props;
|
||||
const { push, location } = useHistory();
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
const emailFromUrl = urlParams.get('email');
|
||||
const emailExistsFromUrl = urlParams.get('email_exists');
|
||||
const defaultEmail = emailFromUrl ? decodeURIComponent(emailFromUrl) : '';
|
||||
const hasPasswordSet = user && user.password_set;
|
||||
|
||||
const [email, setEmail] = useState(defaultEmail);
|
||||
const [syncEnabled, setSyncEnabled] = useState(true);
|
||||
|
||||
const valid = email.match(EMAIL_REGEX);
|
||||
const showEmailVerification = emailToVerify || hasPasswordSet;
|
||||
|
||||
function handleSubmit() {
|
||||
// @if TARGET='app'
|
||||
doSetWalletSyncPreference(syncEnabled);
|
||||
// @endif
|
||||
doUserCheckIfEmailExists(email);
|
||||
}
|
||||
|
||||
function handleChangeToSignIn() {
|
||||
doClearEmailEntry();
|
||||
let url = `/$/${PAGES.AUTH}`;
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
|
||||
urlParams.delete('email_exists');
|
||||
urlParams.delete('email');
|
||||
if (email) {
|
||||
urlParams.set('email', encodeURIComponent(email));
|
||||
}
|
||||
|
||||
push(`${url}?${urlParams.toString()}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classnames('main__sign-in')}>
|
||||
{showEmailVerification ? (
|
||||
<UserEmailVerify />
|
||||
) : (
|
||||
<Card
|
||||
title={__('Cloud Connect')}
|
||||
subtitle={__('Log in to %CLOUD_CONNECT_SITE_NAME%', { CLOUD_CONNECT_SITE_NAME })}
|
||||
actions={
|
||||
<div>
|
||||
<Form onSubmit={handleSubmit} className="section">
|
||||
<FormField
|
||||
autoFocus={!emailExistsFromUrl}
|
||||
placeholder={__('yourstruly@example.com')}
|
||||
type="email"
|
||||
id="username"
|
||||
autoComplete="on"
|
||||
name="sign_in_email"
|
||||
label={__('Email')}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
|
||||
{/* @if TARGET='app' */}
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="sync_checkbox"
|
||||
label={
|
||||
<React.Fragment>
|
||||
{__('Backup your account and wallet data.')}{' '}
|
||||
<Button button="link" href="https://lbry.com/faq/account-sync" label={__('Learn More')} />
|
||||
</React.Fragment>
|
||||
}
|
||||
checked={syncEnabled}
|
||||
onChange={() => setSyncEnabled(!syncEnabled)}
|
||||
/>
|
||||
{/* @endif */}
|
||||
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
autoFocus={emailExistsFromUrl}
|
||||
button="primary"
|
||||
type="submit"
|
||||
label={__('Log In')}
|
||||
disabled={!email || !valid || isPending}
|
||||
/>
|
||||
<Button button="link" onClick={handleChangeToSignIn} label={__('Sign Up')} />
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
}
|
||||
nag={
|
||||
<>
|
||||
{!emailDoesNotExist && emailExistsFromUrl && (
|
||||
<Nag type="helpful" relative message={__('That email is already in use. Did you mean to log in?')} />
|
||||
)}
|
||||
{emailDoesNotExist && (
|
||||
<Nag
|
||||
type="helpful"
|
||||
relative
|
||||
message={__("We can't find that email. Did you mean to sign up?")}
|
||||
actionText={__('Sign Up')}
|
||||
/>
|
||||
)}
|
||||
{!emailExistsFromUrl && !emailDoesNotExist && errorMessage && (
|
||||
<Nag type="error" relative message={errorMessage} />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserEmailReturning;
|
|
@ -1,25 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doUserResendVerificationEmail, doUserCheckEmailVerified } from 'redux/actions/user';
|
||||
import {
|
||||
selectEmailToVerify,
|
||||
selectEmailAlreadyExists,
|
||||
selectUser,
|
||||
selectResendingVerificationEmail,
|
||||
} from 'redux/selectors/user';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import UserEmailVerify from './view';
|
||||
|
||||
const select = state => ({
|
||||
email: selectEmailToVerify(state),
|
||||
isReturningUser: selectEmailAlreadyExists(state),
|
||||
user: selectUser(state),
|
||||
resendingEmail: selectResendingVerificationEmail(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
|
||||
checkEmailVerified: () => dispatch(doUserCheckEmailVerified()),
|
||||
toast: message => dispatch(doToast({ message })),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(UserEmailVerify);
|
|
@ -1,121 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import Button from 'component/button';
|
||||
import UserSignOutButton from 'component/userSignOutButton';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import Card from 'component/common/card';
|
||||
import { SITE_HELP_EMAIL } from 'config';
|
||||
const THIRTY_SECONDS_IN_MS = 30000;
|
||||
type Props = {
|
||||
email: string,
|
||||
isReturningUser: boolean,
|
||||
resendVerificationEmail: (string) => void,
|
||||
resendingEmail: boolean,
|
||||
checkEmailVerified: () => void,
|
||||
toast: (string) => void,
|
||||
user: {
|
||||
has_verified_email: boolean,
|
||||
},
|
||||
};
|
||||
|
||||
type State = {
|
||||
wait: boolean,
|
||||
};
|
||||
|
||||
class UserEmailVerify extends React.PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.emailVerifyCheckInterval = null;
|
||||
this.state = { wait: false };
|
||||
(this: any).handleResendVerificationEmail = this.handleResendVerificationEmail.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.emailVerifyCheckInterval = setInterval(() => {
|
||||
this.checkIfVerified();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { user } = this.props;
|
||||
|
||||
if (this.emailVerifyCheckInterval && user && user.has_verified_email) {
|
||||
clearInterval(this.emailVerifyCheckInterval);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.emailVerifyCheckInterval) {
|
||||
clearInterval(this.emailVerifyCheckInterval);
|
||||
}
|
||||
}
|
||||
|
||||
handleResendVerificationEmail() {
|
||||
const { email, resendVerificationEmail, toast } = this.props;
|
||||
if (!this.state.wait) {
|
||||
resendVerificationEmail(email);
|
||||
toast(__('New email sent.'));
|
||||
this.setState({
|
||||
wait: true,
|
||||
});
|
||||
setTimeout(() => this.setState({ wait: false }), THIRTY_SECONDS_IN_MS);
|
||||
} else {
|
||||
toast(__('Please wait a bit longer before requesting again.'));
|
||||
}
|
||||
}
|
||||
|
||||
checkIfVerified() {
|
||||
const { checkEmailVerified } = this.props;
|
||||
checkEmailVerified();
|
||||
}
|
||||
|
||||
emailVerifyCheckInterval: ?IntervalID;
|
||||
|
||||
render() {
|
||||
const { email, isReturningUser, resendingEmail } = this.props;
|
||||
|
||||
return (
|
||||
<div className="main__sign-up">
|
||||
<Card
|
||||
title={isReturningUser ? __('Check Your email') : __('Confirm your account')}
|
||||
subtitle={
|
||||
<p>
|
||||
{__(
|
||||
'We just sent an email to %email% with a link for you to %verify_text%. Remember to check other email folders like spam or promotions.',
|
||||
{
|
||||
email,
|
||||
verify_text: isReturningUser ? __('log in') : __('verify your account'),
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<React.Fragment>
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('Resend Link')}
|
||||
onClick={this.handleResendVerificationEmail}
|
||||
disabled={resendingEmail}
|
||||
/>
|
||||
<UserSignOutButton label={__('Start Over')} />
|
||||
</div>
|
||||
<p className="help--card-actions">
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
help_link: <Button button="link" href={`mailto:${SITE_HELP_EMAIL}`} label={`${SITE_HELP_EMAIL}`} />,
|
||||
chat_link: <Button button="link" href="https://chat.lbry.com" label={__('chat')} />,
|
||||
}}
|
||||
>
|
||||
Email %help_link% or join our %chat_link% if you encounter any trouble verifying.
|
||||
</I18nMessage>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserEmailVerify;
|
|
@ -1,12 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUser, selectEmailToVerify } from 'redux/selectors/user';
|
||||
import { selectCreatingChannel, selectMyChannelClaims, selectCreateChannelError } from 'redux/selectors/claims';
|
||||
import { doCreateChannel } from 'redux/actions/claims';
|
||||
import UserFirstChannel from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
email: selectEmailToVerify(state),
|
||||
user: selectUser(state),
|
||||
channels: selectMyChannelClaims(state),
|
||||
creatingChannel: selectCreatingChannel(state),
|
||||
createChannelError: selectCreateChannelError(state),
|
||||
|
|
|
@ -15,22 +15,12 @@ type Props = {
|
|||
creatingChannel: boolean,
|
||||
createChannelError: string,
|
||||
claimingReward: boolean,
|
||||
user: User,
|
||||
doToggleInterestedInYoutubeSync: () => void,
|
||||
};
|
||||
|
||||
function UserFirstChannel(props: Props) {
|
||||
const {
|
||||
createChannel,
|
||||
creatingChannel,
|
||||
claimingReward,
|
||||
user,
|
||||
createChannelError,
|
||||
doToggleInterestedInYoutubeSync,
|
||||
} = props;
|
||||
const { primary_email: primaryEmail } = user;
|
||||
const initialChannel = primaryEmail ? primaryEmail.split('@')[0] : '';
|
||||
const [channel, setChannel] = useState(initialChannel);
|
||||
const { createChannel, creatingChannel, claimingReward, createChannelError, doToggleInterestedInYoutubeSync } = props;
|
||||
const [channel, setChannel] = useState(''); // maybe recommend channel based on email when we have it
|
||||
const [nameError, setNameError] = useState(undefined);
|
||||
|
||||
function handleCreateChannel() {
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectPasswordResetSuccess,
|
||||
selectPasswordResetIsPending,
|
||||
selectPasswordResetError,
|
||||
selectEmailToVerify,
|
||||
} from 'redux/selectors/user';
|
||||
import { doUserPasswordReset, doClearPasswordEntry, doClearEmailEntry } from 'redux/actions/user';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import UserSignIn from './view';
|
||||
|
||||
const select = state => ({
|
||||
passwordResetSuccess: selectPasswordResetSuccess(state),
|
||||
passwordResetIsPending: selectPasswordResetIsPending(state),
|
||||
passwordResetError: selectPasswordResetError(state),
|
||||
emailToVerify: selectEmailToVerify(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doUserPasswordReset,
|
||||
doToast,
|
||||
doClearPasswordEntry,
|
||||
doClearEmailEntry,
|
||||
})(UserSignIn);
|
|
@ -1,112 +0,0 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Card from 'component/common/card';
|
||||
import Spinner from 'component/spinner';
|
||||
import { Form, FormField } from 'component/common/form';
|
||||
import { EMAIL_REGEX } from 'constants/email';
|
||||
import ErrorText from 'component/common/error-text';
|
||||
import Button from 'component/button';
|
||||
import Nag from 'component/common/nag';
|
||||
|
||||
type Props = {
|
||||
user: ?User,
|
||||
doToast: ({ message: string }) => void,
|
||||
doUserPasswordReset: string => void,
|
||||
doClearPasswordEntry: () => void,
|
||||
doClearEmailEntry: () => void,
|
||||
passwordResetPending: boolean,
|
||||
passwordResetSuccess: boolean,
|
||||
passwordResetError: ?string,
|
||||
emailToVerify: ?string,
|
||||
};
|
||||
|
||||
function UserPasswordReset(props: Props) {
|
||||
const {
|
||||
doUserPasswordReset,
|
||||
passwordResetPending,
|
||||
passwordResetError,
|
||||
passwordResetSuccess,
|
||||
doToast,
|
||||
doClearPasswordEntry,
|
||||
doClearEmailEntry,
|
||||
emailToVerify,
|
||||
} = props;
|
||||
const { location, push, goBack } = useHistory();
|
||||
const [email, setEmail] = React.useState(emailToVerify || '');
|
||||
const valid = email.match(EMAIL_REGEX);
|
||||
const restartAtSignInPage = location.pathname === `/$/${PAGES.AUTH_SIGNIN}`;
|
||||
|
||||
function handleSubmit() {
|
||||
if (email) {
|
||||
doUserPasswordReset(email);
|
||||
}
|
||||
}
|
||||
|
||||
function handleRestart() {
|
||||
setEmail('');
|
||||
doClearPasswordEntry();
|
||||
doClearEmailEntry();
|
||||
if (restartAtSignInPage) {
|
||||
push(`/$/${PAGES.AUTH_SIGNIN}`);
|
||||
} else {
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (passwordResetSuccess) {
|
||||
doToast({
|
||||
message: __('Email sent!'),
|
||||
});
|
||||
}
|
||||
}, [passwordResetSuccess, doToast]);
|
||||
|
||||
return (
|
||||
<section className="main__sign-in">
|
||||
<Card
|
||||
title={__('Reset your password')}
|
||||
actions={
|
||||
<div>
|
||||
<Form onSubmit={handleSubmit} className="section">
|
||||
<FormField
|
||||
autoFocus
|
||||
disabled={passwordResetSuccess}
|
||||
placeholder={__('yourstruly@example.com')}
|
||||
type="email"
|
||||
name="sign_in_email"
|
||||
id="username"
|
||||
autoComplete="on"
|
||||
label={__('Email')}
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
type="submit"
|
||||
label={passwordResetPending ? __('Resetting') : __('Reset Password')}
|
||||
disabled={!email || !valid || passwordResetPending || passwordResetSuccess}
|
||||
/>
|
||||
<Button button="link" label={__('Cancel')} onClick={handleRestart} />
|
||||
{passwordResetPending && <Spinner type="small" />}
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
}
|
||||
nag={
|
||||
<React.Fragment>
|
||||
{passwordResetError && <Nag type="error" relative message={<ErrorText>{passwordResetError}</ErrorText>} />}
|
||||
{passwordResetSuccess && (
|
||||
<Nag type="helpful" relative message={__('Check your email for a link to reset your password.')} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserPasswordReset;
|
|
@ -1,16 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doClearEmailEntry, doUserFetch } from 'redux/actions/user';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import UserSignIn from './view';
|
||||
|
||||
const select = state => ({
|
||||
// passwordSetSuccess: selectPasswordSetSuccess(state),
|
||||
// passwordSetIsPending: selectPasswordSetIsPending(state),
|
||||
// passwordSetError: selectPasswordSetError(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doToast,
|
||||
doClearEmailEntry,
|
||||
doUserFetch,
|
||||
})(UserSignIn);
|
|
@ -1,108 +0,0 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React from 'react';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import { useHistory } from 'react-router';
|
||||
import Card from 'component/common/card';
|
||||
import { Form, FormField } from 'component/common/form';
|
||||
import ErrorText from 'component/common/error-text';
|
||||
import Button from 'component/button';
|
||||
import Nag from 'component/common/nag';
|
||||
import Spinner from 'component/spinner';
|
||||
|
||||
type Props = {
|
||||
user: ?User,
|
||||
doClearEmailEntry: () => void,
|
||||
doUserFetch: () => void,
|
||||
doToast: ({ message: string }) => void,
|
||||
history: { push: string => void },
|
||||
location: { search: string },
|
||||
passwordSetPending: boolean,
|
||||
passwordSetError: ?string,
|
||||
};
|
||||
|
||||
function UserPasswordReset(props: Props) {
|
||||
const { doClearEmailEntry, doToast, doUserFetch } = props;
|
||||
const { location, push } = useHistory();
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
const email = urlParams.get('email');
|
||||
const authToken = urlParams.get('auth_token');
|
||||
const verificationToken = urlParams.get('verification_token');
|
||||
const [password, setPassword] = React.useState('');
|
||||
const [error, setError] = React.useState();
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
function handleSubmit() {
|
||||
setLoading(true);
|
||||
|
||||
Lbryio.call('user_email', 'confirm', {
|
||||
email: email,
|
||||
verification_token: verificationToken,
|
||||
})
|
||||
.then(() =>
|
||||
Lbryio.call(
|
||||
'user_password',
|
||||
'set',
|
||||
{
|
||||
auth_token: authToken,
|
||||
new_password: password,
|
||||
},
|
||||
'post'
|
||||
)
|
||||
)
|
||||
.then(doUserFetch)
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
doToast({
|
||||
message: __('Password successfully changed!'),
|
||||
});
|
||||
push(`/`);
|
||||
})
|
||||
.catch(error => {
|
||||
setLoading(false);
|
||||
setError(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function handleRestart() {
|
||||
doClearEmailEntry();
|
||||
push(`/$/${PAGES.AUTH_SIGNIN}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="main__sign-in">
|
||||
<Card
|
||||
title={__('Choose a new password')}
|
||||
subtitle={__('Setting a new password for %email%', { email })}
|
||||
actions={
|
||||
<div>
|
||||
<Form onSubmit={handleSubmit} className="section">
|
||||
<FormField
|
||||
autoFocus
|
||||
type="password"
|
||||
name="password_set"
|
||||
label={__('New Password')}
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
type="submit"
|
||||
label={loading ? __('Updating Password') : __('Update Password')}
|
||||
disabled={!password || loading}
|
||||
/>
|
||||
<Button button="link" label={__('Cancel')} onClick={handleRestart} />
|
||||
{loading && <Spinner type="small" />}
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
}
|
||||
nag={error && <Nag type="error" relative message={<ErrorText>{error}</ErrorText>} />}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserPasswordReset;
|
|
@ -1,15 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUser, selectUserIsPending, selectEmailToVerify, selectPasswordExists } from 'redux/selectors/user';
|
||||
import { doUserSignIn } from 'redux/actions/user';
|
||||
import UserSignIn from './view';
|
||||
|
||||
const select = state => ({
|
||||
user: selectUser(state),
|
||||
userFetchPending: selectUserIsPending(state),
|
||||
emailToVerify: selectEmailToVerify(state),
|
||||
passwordExists: selectPasswordExists(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doUserSignIn,
|
||||
})(UserSignIn);
|
|
@ -1,58 +0,0 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import UserEmailReturning from 'component/userEmailReturning';
|
||||
import UserSignInPassword from 'component/userSignInPassword';
|
||||
import Spinner from 'component/spinner';
|
||||
|
||||
type Props = {
|
||||
user: ?User,
|
||||
history: { push: string => void, replace: string => void },
|
||||
location: { search: string },
|
||||
userFetchPending: boolean,
|
||||
doUserSignIn: string => void,
|
||||
emailToVerify: ?string,
|
||||
passwordExists: boolean,
|
||||
};
|
||||
|
||||
function UserSignIn(props: Props) {
|
||||
const { user, location, history, doUserSignIn, userFetchPending, emailToVerify, passwordExists } = props;
|
||||
const { search } = location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const [emailOnlyLogin, setEmailOnlyLogin] = React.useState(false);
|
||||
const hasVerifiedEmail = user && user.has_verified_email;
|
||||
const redirect = urlParams.get('redirect');
|
||||
const showLoading = userFetchPending;
|
||||
const showEmail = !passwordExists || emailOnlyLogin;
|
||||
const showPassword = !showEmail && emailToVerify && passwordExists;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (hasVerifiedEmail || (!showEmail && !showPassword && !showLoading)) {
|
||||
history.replace(redirect || '/');
|
||||
}
|
||||
}, [showEmail, showPassword, showLoading, hasVerifiedEmail]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (emailToVerify && emailOnlyLogin) {
|
||||
doUserSignIn(emailToVerify);
|
||||
}
|
||||
}, [emailToVerify, emailOnlyLogin, doUserSignIn]);
|
||||
|
||||
return (
|
||||
<section>
|
||||
{(showEmail || showPassword) && (
|
||||
<div>
|
||||
{showEmail && <UserEmailReturning />}
|
||||
{showPassword && <UserSignInPassword onHandleEmailOnly={() => setEmailOnlyLogin(true)} />}
|
||||
</div>
|
||||
)}
|
||||
{!showEmail && !showPassword && showLoading && (
|
||||
<div className="main--empty">
|
||||
<Spinner delayed />
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(UserSignIn);
|
|
@ -1,23 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectUser,
|
||||
selectUserIsPending,
|
||||
selectEmailToVerify,
|
||||
selectEmailNewErrorMessage,
|
||||
selectEmailNewIsPending,
|
||||
} from 'redux/selectors/user';
|
||||
import { doUserSignIn, doClearEmailEntry } from 'redux/actions/user';
|
||||
import UserSignIn from './view';
|
||||
|
||||
const select = state => ({
|
||||
user: selectUser(state),
|
||||
userFetchPending: selectUserIsPending(state),
|
||||
emailToVerify: selectEmailToVerify(state),
|
||||
errorMessage: selectEmailNewErrorMessage(state),
|
||||
isPending: selectEmailNewIsPending(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doUserSignIn,
|
||||
doClearEmailEntry,
|
||||
})(UserSignIn);
|
|
@ -1,67 +0,0 @@
|
|||
// @flow
|
||||
import { SITE_NAME } from 'config';
|
||||
import React, { useState } from 'react';
|
||||
import { FormField, Form } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
import Nag from 'component/common/nag';
|
||||
import UserPasswordReset from 'component/userPasswordReset';
|
||||
|
||||
type Props = {
|
||||
errorMessage: ?string,
|
||||
emailToVerify: ?string,
|
||||
doClearEmailEntry: () => void,
|
||||
doUserSignIn: (string, ?string) => void,
|
||||
onHandleEmailOnly: () => void,
|
||||
isPending: boolean,
|
||||
};
|
||||
|
||||
export default function UserSignInPassword(props: Props) {
|
||||
const { errorMessage, doUserSignIn, emailToVerify, onHandleEmailOnly, isPending } = props;
|
||||
const [password, setPassword] = useState('');
|
||||
const [forgotPassword, setForgotPassword] = React.useState(false);
|
||||
|
||||
function handleSubmit() {
|
||||
if (emailToVerify) {
|
||||
doUserSignIn(emailToVerify, password);
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangeToSignIn() {
|
||||
onHandleEmailOnly();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="main__sign-in">
|
||||
{forgotPassword ? (
|
||||
<UserPasswordReset />
|
||||
) : (
|
||||
<Card
|
||||
title={__('Enter your %SITE_NAME% password', { SITE_NAME })}
|
||||
subtitle={__('Logging in as %email%', { email: emailToVerify })}
|
||||
actions={
|
||||
<Form onSubmit={handleSubmit} className="section">
|
||||
<FormField
|
||||
autoFocus
|
||||
type="password"
|
||||
name="sign_in_password"
|
||||
id="password"
|
||||
autoComplete="on"
|
||||
label={__('Password')}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
helper={<Button button="link" label={__('Forgot Password?')} onClick={() => setForgotPassword(true)} />}
|
||||
/>
|
||||
|
||||
<div className="section__actions">
|
||||
<Button button="primary" type="submit" label={__('Continue')} disabled={!password || isPending} />
|
||||
<Button button="link" onClick={handleChangeToSignIn} label={__('Use Magic Link')} />
|
||||
</div>
|
||||
</Form>
|
||||
}
|
||||
nag={errorMessage && <Nag type="error" relative message={errorMessage} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doSignOut } from 'redux/actions/app';
|
||||
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
|
||||
import UserSignOutButton from './view';
|
||||
|
||||
const select = state => ({});
|
||||
|
||||
export default connect(select, {
|
||||
doSignOut,
|
||||
doClearEmailEntry,
|
||||
doClearPasswordEntry,
|
||||
})(UserSignOutButton);
|
|
@ -1,29 +0,0 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
button: string,
|
||||
label?: string,
|
||||
doSignOut: () => void,
|
||||
doClearEmailEntry: () => void,
|
||||
doClearPasswordEntry: () => void,
|
||||
};
|
||||
|
||||
function UserSignOutButton(props: Props) {
|
||||
const { button = 'link', doSignOut, doClearEmailEntry, doClearPasswordEntry, label } = props;
|
||||
|
||||
return (
|
||||
<Button
|
||||
button={button}
|
||||
label={label || __('Sign Out')}
|
||||
onClick={() => {
|
||||
doClearPasswordEntry();
|
||||
doClearEmailEntry();
|
||||
doSignOut();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserSignOutButton;
|
|
@ -1,65 +0,0 @@
|
|||
import REWARD_TYPES from 'rewards';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectGetSyncIsPending, selectSyncHash, selectPrefsReady } from 'redux/selectors/sync';
|
||||
import { doClaimRewardType } from 'redux/actions/rewards';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { selectClaimedRewards, makeSelectIsRewardClaimPending } from 'redux/selectors/rewards';
|
||||
import { doUserFetch } from 'redux/actions/user';
|
||||
import {
|
||||
selectUserIsPending,
|
||||
selectYoutubeChannels,
|
||||
selectEmailToVerify,
|
||||
selectUser,
|
||||
selectAccessToken,
|
||||
} from 'redux/selectors/user';
|
||||
import { selectMyChannelClaims, selectFetchingMyChannels, selectCreatingChannel } from 'redux/selectors/claims';
|
||||
import { selectBalance } from 'redux/selectors/wallet';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { selectInterestedInYoutubeSync } from 'redux/selectors/app';
|
||||
import { doToggleInterestedInYoutubeSync } from 'redux/actions/app';
|
||||
import UserSignIn from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
emailToVerify: selectEmailToVerify(state),
|
||||
user: selectUser(state),
|
||||
accessToken: selectAccessToken(state),
|
||||
channels: selectMyChannelClaims(state),
|
||||
claimedRewards: selectClaimedRewards(state),
|
||||
claimingReward: makeSelectIsRewardClaimPending()(state, {
|
||||
reward_type: REWARD_TYPES.TYPE_CONFIRM_EMAIL,
|
||||
}),
|
||||
balance: selectBalance(state),
|
||||
fetchingChannels: selectFetchingMyChannels(state),
|
||||
youtubeChannels: selectYoutubeChannels(state),
|
||||
userFetchPending: selectUserIsPending(state),
|
||||
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
|
||||
followingAcknowledged: makeSelectClientSetting(SETTINGS.FOLLOWING_ACKNOWLEDGED)(state),
|
||||
tagsAcknowledged: makeSelectClientSetting(SETTINGS.TAGS_ACKNOWLEDGED)(state),
|
||||
rewardsAcknowledged: makeSelectClientSetting(SETTINGS.REWARDS_ACKNOWLEDGED)(state),
|
||||
syncingWallet: selectGetSyncIsPending(state),
|
||||
hasSynced: Boolean(selectSyncHash(state)),
|
||||
creatingChannel: selectCreatingChannel(state),
|
||||
interestedInYoutubeSync: selectInterestedInYoutubeSync(state),
|
||||
prefsReady: selectPrefsReady(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
fetchUser: () => dispatch(doUserFetch()),
|
||||
claimConfirmEmailReward: () =>
|
||||
dispatch(
|
||||
doClaimRewardType(REWARD_TYPES.TYPE_CONFIRM_EMAIL, {
|
||||
notifyError: false,
|
||||
})
|
||||
),
|
||||
claimNewUserReward: () =>
|
||||
dispatch(
|
||||
doClaimRewardType(REWARD_TYPES.NEW_USER, {
|
||||
notifyError: false,
|
||||
})
|
||||
),
|
||||
setClientSetting: (setting, value, pushToPrefs) => dispatch(doSetClientSetting(setting, value, pushToPrefs)),
|
||||
doToggleInterestedInYoutubeSync: () => dispatch(doToggleInterestedInYoutubeSync()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(UserSignIn);
|
|
@ -1,232 +0,0 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { useHistory } from 'react-router';
|
||||
import UserEmailNew from 'component/userEmailNew';
|
||||
import UserEmailVerify from 'component/userEmailVerify';
|
||||
import UserChannelFollowIntro from 'component/userChannelFollowIntro';
|
||||
import UserTagFollowIntro from 'component/userTagFollowIntro';
|
||||
import REWARDS from 'rewards';
|
||||
import Spinner from 'component/spinner';
|
||||
import useFetched from 'effects/use-fetched';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
import { SHOW_TAGS_INTRO } from 'config';
|
||||
|
||||
const REDIRECT_PARAM = 'redirect';
|
||||
const REDIRECT_IMMEDIATELY_PARAM = 'immediate';
|
||||
const STEP_PARAM = 'step';
|
||||
|
||||
type Props = {
|
||||
user: ?User,
|
||||
emailToVerify: ?string,
|
||||
channels: ?Array<string>,
|
||||
balance: ?number,
|
||||
fetchingChannels: boolean,
|
||||
claimingReward: boolean,
|
||||
claimConfirmEmailReward: () => void,
|
||||
claimNewUserReward: () => void,
|
||||
fetchUser: () => void,
|
||||
claimedRewards: Array<Reward>,
|
||||
youtubeChannels: Array<any>,
|
||||
syncEnabled: boolean,
|
||||
hasSynced: boolean,
|
||||
syncingWallet: boolean,
|
||||
creatingChannel: boolean,
|
||||
setClientSetting: (string, boolean, ?boolean) => void,
|
||||
followingAcknowledged: boolean,
|
||||
tagsAcknowledged: boolean,
|
||||
rewardsAcknowledged: boolean,
|
||||
interestedInYoutubeSync: boolean,
|
||||
doToggleInterestedInYoutubeSync: () => void,
|
||||
prefsReady: boolean,
|
||||
};
|
||||
|
||||
function UserSignUp(props: Props) {
|
||||
const {
|
||||
emailToVerify,
|
||||
user,
|
||||
claimingReward,
|
||||
claimedRewards,
|
||||
claimConfirmEmailReward,
|
||||
claimNewUserReward,
|
||||
balance,
|
||||
fetchUser,
|
||||
syncEnabled,
|
||||
syncingWallet,
|
||||
hasSynced,
|
||||
fetchingChannels,
|
||||
creatingChannel,
|
||||
followingAcknowledged,
|
||||
tagsAcknowledged,
|
||||
rewardsAcknowledged,
|
||||
setClientSetting,
|
||||
interestedInYoutubeSync,
|
||||
doToggleInterestedInYoutubeSync,
|
||||
prefsReady,
|
||||
} = props;
|
||||
const {
|
||||
location: { search, pathname },
|
||||
replace,
|
||||
} = useHistory();
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const redirect = urlParams.get(REDIRECT_PARAM);
|
||||
const step = urlParams.get(STEP_PARAM);
|
||||
const shouldRedirectImmediately = urlParams.get(REDIRECT_IMMEDIATELY_PARAM);
|
||||
const [initialSignInStep, setInitialSignInStep] = React.useState();
|
||||
const hasVerifiedEmail = user && user.has_verified_email;
|
||||
const passwordSet = user && user.password_set;
|
||||
const hasFetchedReward = useFetched(claimingReward);
|
||||
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
|
||||
const hasClaimedEmailAward = claimedRewards.some((reward) => reward.reward_type === REWARDS.TYPE_CONFIRM_EMAIL);
|
||||
// Complexity warning
|
||||
// We can't just check if we are currently fetching something
|
||||
// We may want to keep a component rendered while something is being fetched, instead of replacing it with the large spinner
|
||||
// The verbose variable names are an attempt to alleviate _some_ of the confusion from handling all edge cases that come from
|
||||
// reward claiming, channel creation, account syncing, and youtube transfer
|
||||
// The possible screens for the sign in flow
|
||||
const showEmail = !hasVerifiedEmail;
|
||||
const showEmailVerification = (emailToVerify && !hasVerifiedEmail) || (!hasVerifiedEmail && passwordSet);
|
||||
const showFollowIntro = step === 'channels' || (hasVerifiedEmail && !followingAcknowledged);
|
||||
const showTagsIntro = SHOW_TAGS_INTRO && (step === 'tags' || (hasVerifiedEmail && !tagsAcknowledged));
|
||||
const canHijackSignInFlowWithSpinner = hasVerifiedEmail && !showFollowIntro && !showTagsIntro && !rewardsAcknowledged;
|
||||
const isCurrentlyFetchingSomething = fetchingChannels || claimingReward || syncingWallet || creatingChannel;
|
||||
const isWaitingForSomethingToFinish =
|
||||
// If the user has claimed the email award, we need to wait until the balance updates sometime in the future
|
||||
(!hasFetchedReward && !hasClaimedEmailAward) || (syncEnabled && !hasSynced);
|
||||
const showLoadingSpinner =
|
||||
canHijackSignInFlowWithSpinner && (isCurrentlyFetchingSomething || isWaitingForSomethingToFinish);
|
||||
|
||||
function setSettingAndSync(setting, value) {
|
||||
setClientSetting(setting, value, true);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchUser();
|
||||
}, [fetchUser]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (previousHasVerifiedEmail === false && hasVerifiedEmail && prefsReady) {
|
||||
setSettingAndSync(SETTINGS.FIRST_RUN_STARTED, true);
|
||||
}
|
||||
}, [hasVerifiedEmail, previousHasVerifiedEmail, prefsReady]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Don't claim the reward if sync is enabled until after a sync has been completed successfully
|
||||
// If we do it before, we could end up trying to sync a wallet with a non-zero balance which will fail to sync
|
||||
const delayForSync = syncEnabled && !hasSynced;
|
||||
|
||||
if (hasVerifiedEmail && !hasClaimedEmailAward && !hasFetchedReward && !delayForSync) {
|
||||
claimConfirmEmailReward();
|
||||
}
|
||||
}, [
|
||||
hasVerifiedEmail,
|
||||
claimConfirmEmailReward,
|
||||
hasClaimedEmailAward,
|
||||
hasFetchedReward,
|
||||
syncEnabled,
|
||||
hasSynced,
|
||||
balance,
|
||||
]);
|
||||
|
||||
// Loop through this list from the end, until it finds a matching component
|
||||
// If it never finds one, assume the user has completed every step and redirect them
|
||||
const SIGN_IN_FLOW = [
|
||||
showEmail && (
|
||||
<UserEmailNew
|
||||
interestedInYoutubSync={interestedInYoutubeSync}
|
||||
doToggleInterestedInYoutubeSync={doToggleInterestedInYoutubeSync}
|
||||
/>
|
||||
),
|
||||
showEmailVerification && <UserEmailVerify />,
|
||||
showFollowIntro && (
|
||||
<UserChannelFollowIntro
|
||||
onContinue={() => {
|
||||
if (urlParams.get('reset_scroll')) {
|
||||
urlParams.delete('reset_scroll');
|
||||
urlParams.append('reset_scroll', '2');
|
||||
}
|
||||
|
||||
urlParams.delete(STEP_PARAM);
|
||||
|
||||
setSettingAndSync(SETTINGS.FOLLOWING_ACKNOWLEDGED, true);
|
||||
replace(`${pathname}?${urlParams.toString()}`);
|
||||
}}
|
||||
onBack={() => {
|
||||
if (urlParams.get('reset_scroll')) {
|
||||
urlParams.delete('reset_scroll');
|
||||
urlParams.append('reset_scroll', '3');
|
||||
}
|
||||
|
||||
setSettingAndSync(SETTINGS.FOLLOWING_ACKNOWLEDGED, false);
|
||||
replace(`${pathname}?${urlParams.toString()}`);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
showTagsIntro && (
|
||||
<UserTagFollowIntro
|
||||
onContinue={() => {
|
||||
let url = `/$/${PAGES.AUTH}?reset_scroll=1&${STEP_PARAM}=channels`;
|
||||
if (redirect) {
|
||||
url += `&${REDIRECT_PARAM}=${redirect}`;
|
||||
}
|
||||
if (shouldRedirectImmediately) {
|
||||
url += `&${REDIRECT_IMMEDIATELY_PARAM}=true`;
|
||||
}
|
||||
|
||||
replace(url);
|
||||
setSettingAndSync(SETTINGS.TAGS_ACKNOWLEDGED, true);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
showLoadingSpinner && (
|
||||
<div className="main--empty">
|
||||
<Spinner />
|
||||
</div>
|
||||
),
|
||||
];
|
||||
|
||||
// $FlowFixMe
|
||||
function getSignInStep() {
|
||||
for (var i = SIGN_IN_FLOW.length - 1; i > -1; i--) {
|
||||
const Component = SIGN_IN_FLOW[i];
|
||||
if (Component) {
|
||||
// If we want to redirect immediately,
|
||||
// remember the first step so we can redirect once a new step has been reached
|
||||
// Ignore the loading step
|
||||
if (redirect && shouldRedirectImmediately) {
|
||||
if (!initialSignInStep) {
|
||||
setInitialSignInStep(i);
|
||||
} else if (i !== initialSignInStep && i !== SIGN_IN_FLOW.length - 1) {
|
||||
replace(redirect);
|
||||
}
|
||||
}
|
||||
|
||||
const scrollableSteps = [2, 4, 5];
|
||||
const isScrollable = scrollableSteps.includes(i);
|
||||
return [Component, isScrollable];
|
||||
}
|
||||
}
|
||||
|
||||
return [undefined, false];
|
||||
}
|
||||
|
||||
const [componentToRender, isScrollable] = getSignInStep();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!componentToRender) {
|
||||
claimNewUserReward();
|
||||
}
|
||||
}, [componentToRender, claimNewUserReward]);
|
||||
|
||||
if (!componentToRender) {
|
||||
replace(redirect || '/');
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={classnames('main--contained', { 'main--hoisted': isScrollable })}>{componentToRender}</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserSignUp;
|
|
@ -6,23 +6,15 @@ import {
|
|||
} from 'redux/selectors/collections';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import * as COLLECTIONS_CONSTS from 'constants/collections';
|
||||
import {
|
||||
doChangeVolume,
|
||||
doChangeMute,
|
||||
doAnalyticsView,
|
||||
doAnalyticsBuffer,
|
||||
doAnaltyicsPurchaseEvent,
|
||||
} from 'redux/actions/app';
|
||||
import { doChangeVolume, doChangeMute, doAnalyticsView, doAnaltyicsPurchaseEvent } from 'redux/actions/app';
|
||||
import { selectVolume, selectMute } from 'redux/selectors/app';
|
||||
import { savePosition, clearPosition, doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
||||
import { makeSelectContentPositionForUri, makeSelectIsPlayerFloating, selectPlayingUri } from 'redux/selectors/content';
|
||||
import { selectRecommendedContentForUri } from 'redux/selectors/search';
|
||||
import VideoViewer from './view';
|
||||
import { withRouter } from 'react-router';
|
||||
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
||||
import { selectDaemonSettings, makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
|
||||
import { toggleVideoTheaterMode, toggleAutoplayNext, doSetClientSetting } from 'redux/actions/settings';
|
||||
import { selectUser } from 'redux/selectors/user';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { search } = props.location;
|
||||
|
@ -30,7 +22,6 @@ const select = (state, props) => {
|
|||
const uri = props.uri;
|
||||
// TODO: eventually this should be received from DB and not local state (https://github.com/lbryio/lbry-desktop/issues/6796)
|
||||
const position = urlParams.get('t') !== null ? urlParams.get('t') : makeSelectContentPositionForUri(uri)(state);
|
||||
const userId = selectUser(state) && selectUser(state).id;
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const collectionId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID) || (playingUri && playingUri.collectionId);
|
||||
const isMarkdownOrComment = playingUri && (playingUri.source === 'markdown' || playingUri.source === 'comment');
|
||||
|
@ -47,7 +38,6 @@ const select = (state, props) => {
|
|||
|
||||
return {
|
||||
position,
|
||||
userId,
|
||||
collectionId,
|
||||
nextRecommendedUri,
|
||||
previousListUri,
|
||||
|
@ -71,8 +61,6 @@ const perform = (dispatch) => ({
|
|||
clearPosition: (uri) => dispatch(clearPosition(uri)),
|
||||
changeMute: (muted) => dispatch(doChangeMute(muted)),
|
||||
doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
||||
doAnalyticsBuffer: (uri, bufferData) => dispatch(doAnalyticsBuffer(uri, bufferData)),
|
||||
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||
toggleVideoTheaterMode: () => dispatch(toggleVideoTheaterMode()),
|
||||
toggleAutoplayNext: () => dispatch(toggleAutoplayNext()),
|
||||
setVideoPlaybackRate: (rate) => dispatch(doSetClientSetting(SETTINGS.VIDEO_PLAYBACK_RATE, rate)),
|
||||
|
|
|
@ -1,178 +0,0 @@
|
|||
// Created by xander on 6/21/2021
|
||||
import videojs from 'video.js';
|
||||
|
||||
import RecSys from 'extras/recsys/recsys';
|
||||
const VERSION = '0.0.1';
|
||||
|
||||
/* RecSys */
|
||||
const PlayerEvent = {
|
||||
event: {
|
||||
start: 0, // event types
|
||||
stop: 1,
|
||||
scrub: 2,
|
||||
speed: 3,
|
||||
},
|
||||
};
|
||||
|
||||
function newRecsysPlayerEvent(eventType, offset, arg) {
|
||||
if (arg) {
|
||||
return {
|
||||
event: eventType,
|
||||
offset: offset,
|
||||
arg: arg,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
event: eventType,
|
||||
offset: offset,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
videoId: null,
|
||||
userId: 0,
|
||||
debug: false,
|
||||
embedded: false,
|
||||
};
|
||||
|
||||
const Component = videojs.getComponent('Component');
|
||||
const registerPlugin = videojs.registerPlugin || videojs.plugin;
|
||||
|
||||
class RecsysPlugin extends Component {
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
// Plugin started
|
||||
if (options.debug) {
|
||||
this.log(`Created recsys plugin for: videoId:${options.videoId}`);
|
||||
}
|
||||
|
||||
// To help with debugging, we'll add a global vjs object with the video js player
|
||||
window.vjs = player;
|
||||
|
||||
this.player = player;
|
||||
this.lastTimeUpdate = null;
|
||||
this.currentTimeUpdate = null;
|
||||
this.inPause = false;
|
||||
|
||||
// Plugin event listeners
|
||||
player.on('playing', (event) => this.onPlay(event));
|
||||
player.on('pause', (event) => this.onPause(event));
|
||||
player.on('ended', (event) => this.onEnded(event));
|
||||
player.on('ratechange', (event) => this.onRateChange(event));
|
||||
player.on('timeupdate', (event) => this.onTimeUpdate(event));
|
||||
player.on('seeked', (event) => this.onSeeked(event));
|
||||
|
||||
// Event trigger to send recsys event
|
||||
player.on('dispose', (event) => this.onDispose(event));
|
||||
}
|
||||
|
||||
onPlay(event) {
|
||||
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.start, this.player.currentTime());
|
||||
this.log('onPlay', recsysEvent);
|
||||
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent, this.options_.embedded);
|
||||
|
||||
this.inPause = false;
|
||||
this.lastTimeUpdate = recsysEvent.offset;
|
||||
}
|
||||
|
||||
onPause(event) {
|
||||
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.stop, this.player.currentTime());
|
||||
this.log('onPause', recsysEvent);
|
||||
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
|
||||
|
||||
this.inPause = true;
|
||||
}
|
||||
|
||||
onEnded(event) {
|
||||
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.stop, this.player.currentTime());
|
||||
this.log('onEnded', recsysEvent);
|
||||
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
|
||||
}
|
||||
|
||||
onRateChange(event) {
|
||||
const recsysEvent = newRecsysPlayerEvent(
|
||||
PlayerEvent.event.speed,
|
||||
this.player.currentTime(),
|
||||
this.player.playbackRate()
|
||||
);
|
||||
this.log('onRateChange', recsysEvent);
|
||||
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
|
||||
}
|
||||
|
||||
onTimeUpdate(event) {
|
||||
const nextCurrentTime = this.player.currentTime();
|
||||
|
||||
if (!this.inPause && Math.abs(this.lastTimeUpdate - nextCurrentTime) < 0.5) {
|
||||
// Don't update lastTimeUpdate if we are in a pause segment.
|
||||
//
|
||||
// However, if we aren't in a pause and the time jumped
|
||||
// the onTimeUpdate event probably fired before the pause and seek.
|
||||
// Don't update in that case, either.
|
||||
this.lastTimeUpdate = this.currentTimeUpdate;
|
||||
}
|
||||
|
||||
this.currentTimeUpdate = nextCurrentTime;
|
||||
}
|
||||
|
||||
onSeeked(event) {
|
||||
const curTime = this.player.currentTime();
|
||||
|
||||
// There are three patterns for seeking:
|
||||
//
|
||||
// Assuming the video is playing,
|
||||
//
|
||||
// 1. Dragging the player head emits: onPause -> onSeeked -> onSeeked -> ... -> onPlay
|
||||
// 2. Key press left right emits: onSeeked -> onPlay
|
||||
// 3. Clicking a position emits: onPause -> onSeeked -> onPlay
|
||||
//
|
||||
// If the video is NOT playing,
|
||||
//
|
||||
// 1. Dragging the player head emits: onSeeked
|
||||
// 2. Key press left right emits: onSeeked
|
||||
// 3. Clicking a position emits: onSeeked
|
||||
const fromTime = this.lastTimeUpdate;
|
||||
|
||||
if (fromTime !== curTime) {
|
||||
// This removes duplicates that aren't useful.
|
||||
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.scrub, fromTime, curTime);
|
||||
this.log('onSeeked', recsysEvent);
|
||||
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
|
||||
}
|
||||
}
|
||||
|
||||
onDispose(event) {
|
||||
RecSys.onPlayerDispose(this.options_.videoId, this.options_.embedded);
|
||||
}
|
||||
|
||||
log(...args) {
|
||||
if (this.options_.debug) {
|
||||
console.log(`Recsys Player Debug:`, JSON.stringify(args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
videojs.registerComponent('recsys', RecsysPlugin);
|
||||
|
||||
const onPlayerReady = (player, options) => {
|
||||
player.recsys = new RecsysPlugin(player, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the plugin.
|
||||
*
|
||||
* @function plugin
|
||||
* @param {Object} [options={}]
|
||||
*/
|
||||
const plugin = function (options) {
|
||||
this.ready(() => {
|
||||
onPlayerReady(this, videojs.mergeOptions(defaults, options));
|
||||
});
|
||||
};
|
||||
|
||||
plugin.VERSION = VERSION;
|
||||
|
||||
registerPlugin('recsys', plugin);
|
||||
|
||||
export default plugin;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue