Compare commits

...

6 commits

171 changed files with 1748 additions and 6463 deletions

View file

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

View file

@ -13,6 +13,7 @@ const config = {
LBRY_API_URL: process.env.LBRY_API_URL, //api.lbry.com', 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_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //player.odysee.com
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API, LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
LBRYSYNC_API: process.env.LBRYSYNC_API,
SEARCH_SERVER_API: process.env.SEARCH_SERVER_API, SEARCH_SERVER_API: process.env.SEARCH_SERVER_API,
CLOUD_CONNECT_SITE_NAME: process.env.CLOUD_CONNECT_SITE_NAME, CLOUD_CONNECT_SITE_NAME: process.env.CLOUD_CONNECT_SITE_NAME,
COMMENT_SERVER_API: process.env.COMMENT_SERVER_API, COMMENT_SERVER_API: process.env.COMMENT_SERVER_API,

View file

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

View file

@ -7,7 +7,6 @@ import https from 'https';
import { app, dialog, ipcMain, session, shell, BrowserWindow } from 'electron'; import { app, dialog, ipcMain, session, shell, BrowserWindow } from 'electron';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
import Lbry from 'lbry'; import Lbry from 'lbry';
import LbryFirstInstance from './LbryFirstInstance';
import Daemon from './Daemon'; import Daemon from './Daemon';
import isDev from 'electron-is-dev'; import isDev from 'electron-is-dev';
import createTray from './createTray'; import createTray from './createTray';
@ -18,6 +17,7 @@ import installDevtools from './installDevtools';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { diskSpaceLinux, diskSpaceWindows, diskSpaceMac } from '../ui/util/diskspace'; import { diskSpaceLinux, diskSpaceWindows, diskSpaceMac } from '../ui/util/diskspace';
import { generateSalt, generateSaltSeed, deriveSecrets, walletHmac } from './sync/sync.js';
const { download } = require('electron-dl'); const { download } = require('electron-dl');
const mime = require('mime'); const mime = require('mime');
@ -58,7 +58,6 @@ let rendererWindow;
let tray; // eslint-disable-line let tray; // eslint-disable-line
let daemon; let daemon;
let lbryFirst;
const appState = {}; const appState = {};
const PROTOCOL = 'lbry'; 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 // When we are starting the app, ensure there are no other apps already running
const gotSingleInstanceLock = app.requestSingleInstanceLock(); const gotSingleInstanceLock = app.requestSingleInstanceLock();
@ -271,10 +225,6 @@ app.on('will-quit', event => {
daemon.quit(); daemon.quit();
event.preventDefault(); event.preventDefault();
} }
if (lbryFirst) {
lbryFirst.quit();
event.preventDefault();
}
if (rendererWindow) { if (rendererWindow) {
tray.destroy(); 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', () => { ipcMain.on('version-info-requested', () => {
function formatRc(ver) { function formatRc(ver) {
// Adds dash if needed to make RC suffix SemVer friendly // Adds dash if needed to make RC suffix SemVer friendly
@ -453,15 +439,6 @@ ipcMain.on('version-info-requested', () => {
requestLatestRelease(); 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 => { process.on('uncaughtException', error => {
console.log(error); console.log(error);
dialog.showErrorBox('Error Encountered', `Caught error: ${error}`); dialog.showErrorBox('Error Encountered', `Caught error: ${error}`);
@ -628,3 +605,5 @@ ipcMain.on('upgrade', (event, installerPath) => {
}); });
app.quit(); app.quit();
}); });

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

View file

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

View file

@ -1,3 +0,0 @@
import Recsys from './recsys';
export default Recsys;

View file

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

View file

@ -192,6 +192,7 @@
"semver": "^5.3.0", "semver": "^5.3.0",
"strip-markdown": "^3.0.3", "strip-markdown": "^3.0.3",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"tape": "^5.6.0",
"terser-webpack-plugin": "^4.2.3", "terser-webpack-plugin": "^4.2.3",
"three-full": "^28.0.2", "three-full": "^28.0.2",
"unist-util-visit": "^2.0.3", "unist-util-visit": "^2.0.3",

View file

@ -2318,5 +2318,29 @@
"Odysee Connect --[Section in Help Page]--": "Odysee Connect", "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 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.", "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--" "--end--": "--end--"
} }

View file

@ -51,7 +51,6 @@ type Analytics = {
) => Promise<any>, ) => Promise<any>,
emailProvidedEvent: () => void, emailProvidedEvent: () => void,
emailVerifiedEvent: () => void, emailVerifiedEvent: () => void,
rewardEligibleEvent: () => void,
startupEvent: () => void, startupEvent: () => void,
purchaseEvent: (number) => void, purchaseEvent: (number) => void,
readyEvent: (number) => void, readyEvent: (number) => void,
@ -355,9 +354,6 @@ const analytics: Analytics = {
emailVerifiedEvent: () => { emailVerifiedEvent: () => {
sendMatomoEvent('Engagement', 'Email-Verified'); sendMatomoEvent('Engagement', 'Email-Verified');
}, },
rewardEligibleEvent: () => {
sendMatomoEvent('Engagement', 'Reward-Eligible');
},
openUrlEvent: (url: string) => { openUrlEvent: (url: string) => {
sendMatomoEvent('Engagement', 'Open-Url', url); sendMatomoEvent('Engagement', 'Open-Url', url);
}, },

View file

@ -1,9 +1,6 @@
import { hot } from 'react-hot-loader/root'; import { hot } from 'react-hot-loader/root';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectGetSyncErrorMessage, selectSyncFatalError } from 'redux/selectors/sync'; 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 { doFetchChannelListMine, doFetchCollectionListMine, doResolveUris } from 'redux/actions/claims';
import { selectMyChannelUrls, selectMyChannelClaimIds } from 'redux/selectors/claims'; import { selectMyChannelUrls, selectMyChannelClaimIds } from 'redux/selectors/claims';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
@ -25,7 +22,7 @@ import { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings
import { doSyncLoop } from 'redux/actions/sync'; import { doSyncLoop } from 'redux/actions/sync';
import { import {
doDownloadUpgradeRequested, doDownloadUpgradeRequested,
doSignIn, doSignIn, // huh
doGetAndPopulatePreferences, doGetAndPopulatePreferences,
doSetActiveChannel, doSetActiveChannel,
doSetIncognito, doSetIncognito,
@ -34,8 +31,6 @@ import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/c
import App from './view'; import App from './view';
const select = (state) => ({ const select = (state) => ({
user: selectUser(state),
accessToken: selectAccessToken(state),
theme: selectThemePath(state), theme: selectThemePath(state),
language: selectLanguage(state), language: selectLanguage(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state), syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
@ -43,8 +38,6 @@ const select = (state) => ({
autoUpdateDownloaded: selectAutoUpdateDownloaded(state), autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state), isUpgradeAvailable: selectIsUpgradeAvailable(state),
syncError: selectGetSyncErrorMessage(state), syncError: selectGetSyncErrorMessage(state),
rewards: selectUnclaimedRewards(state),
isAuthenticated: selectUserVerifiedEmail(state),
currentModal: selectModal(state), currentModal: selectModal(state),
syncFatalError: selectSyncFatalError(state), syncFatalError: selectSyncFatalError(state),
activeChannelClaim: selectActiveChannelClaim(state), activeChannelClaim: selectActiveChannelClaim(state),
@ -55,7 +48,6 @@ const select = (state) => ({
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({
fetchAccessToken: () => dispatch(doFetchAccessToken()),
fetchChannelListMine: () => dispatch(doFetchChannelListMine()), fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
fetchCollectionListMine: () => dispatch(doFetchCollectionListMine()), fetchCollectionListMine: () => dispatch(doFetchCollectionListMine()),
setLanguage: (language) => dispatch(doSetLanguage(language)), setLanguage: (language) => dispatch(doSetLanguage(language)),
@ -64,7 +56,6 @@ const perform = (dispatch) => ({
updatePreferences: () => dispatch(doGetAndPopulatePreferences()), updatePreferences: () => dispatch(doGetAndPopulatePreferences()),
getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()), getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()),
syncLoop: (noInterval) => dispatch(doSyncLoop(noInterval)), syncLoop: (noInterval) => dispatch(doSyncLoop(noInterval)),
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()), setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()),
setIncognito: () => dispatch(doSetIncognito()), setIncognito: () => dispatch(doSetIncognito()),
fetchModBlockedList: () => dispatch(doFetchModBlockedList()), fetchModBlockedList: () => dispatch(doFetchModBlockedList()),

View file

@ -1,16 +1,12 @@
// @flow // @flow
import * as PAGES from 'constants/pages';
import React, { useEffect, useRef, useState, useLayoutEffect } from 'react'; import React, { useEffect, useRef, useState, useLayoutEffect } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import analytics from 'analytics';
import Router from 'component/router/index'; import Router from 'component/router/index';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import { openContextMenu } from 'util/context-menu'; import { openContextMenu } from 'util/context-menu';
import useKonamiListener from 'util/enhanced-layout'; import useKonamiListener from 'util/enhanced-layout';
import FileRenderFloating from 'component/fileRenderFloating'; import FileRenderFloating from 'component/fileRenderFloating';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import usePrevious from 'effects/use-previous';
import REWARDS from 'rewards';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import LANGUAGES from 'constants/languages'; import LANGUAGES from 'constants/languages';
import useZoom from 'effects/use-zoom'; import useZoom from 'effects/use-zoom';
@ -46,7 +42,6 @@ type Props = {
length: number, length: number,
push: (string) => void, push: (string) => void,
}, },
fetchAccessToken: () => void,
fetchChannelListMine: () => void, fetchChannelListMine: () => void,
fetchCollectionListMine: () => void, fetchCollectionListMine: () => void,
signIn: () => void, signIn: () => void,
@ -82,27 +77,18 @@ type Props = {
function App(props: Props) { function App(props: Props) {
const { const {
theme, theme,
user,
fetchAccessToken,
fetchChannelListMine, fetchChannelListMine,
fetchCollectionListMine, fetchCollectionListMine,
signIn,
autoUpdateDownloaded, autoUpdateDownloaded,
isUpgradeAvailable, isUpgradeAvailable,
requestDownloadUpgrade, requestDownloadUpgrade,
uploadCount, uploadCount,
history, history,
syncError,
language, language,
languages, languages,
setLanguage, setLanguage,
updatePreferences, updatePreferences,
getWalletSyncPref, getWalletSyncPref,
rewards,
setReferrer,
isAuthenticated,
syncLoop,
currentModal,
syncFatalError, syncFatalError,
myChannelClaimIds, myChannelClaimIds,
activeChannelId, activeChannelId,
@ -117,38 +103,16 @@ function App(props: Props) {
const appRef = useRef(); const appRef = useRef();
const isEnhancedLayout = useKonamiListener(); 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 [upgradeNagClosed, setUpgradeNagClosed] = useState(false);
const [resolvedSubscriptions, setResolvedSubscriptions] = useState(false); const [resolvedSubscriptions, setResolvedSubscriptions] = useState(false);
// const [retryingSync, setRetryingSync] = useState(false);
const [langRenderKey, setLangRenderKey] = useState(0); const [langRenderKey, setLangRenderKey] = useState(0);
const [sidebarOpen] = usePersistedState('sidebar', true); const [sidebarOpen] = usePersistedState('sidebar', true);
const showUpgradeButton = (autoUpdateDownloaded || isUpgradeAvailable) && !upgradeNagClosed; 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 useCustomScrollbar = !IS_MAC;
const hasMyChannels = myChannelClaimIds && myChannelClaimIds.length > 0; const hasMyChannels = myChannelClaimIds && myChannelClaimIds.length > 0;
const hasNoChannels = myChannelClaimIds && myChannelClaimIds.length === 0; const hasNoChannels = myChannelClaimIds && myChannelClaimIds.length === 0;
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language]; const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
const hasActiveChannelClaim = activeChannelId !== undefined; const hasActiveChannelClaim = activeChannelId !== undefined;
const isPersonalized = hasVerifiedEmail;
useEffect(() => {
if (userId) {
analytics.setUser(userId);
}
}, [userId]);
useEffect(() => { useEffect(() => {
if (!uploadCount) return; if (!uploadCount) return;
@ -188,23 +152,10 @@ function App(props: Props) {
}, []); }, []);
// Enable ctrl +/- zooming on Desktop. // Enable ctrl +/- zooming on Desktop.
// @if TARGET='app'
useZoom(); useZoom();
// @endif
// Enable 'Alt + Left/Right' for history navigation on Desktop. // Enable 'Alt + Left/Right' for history navigation on Desktop.
// @if TARGET='app'
useHistoryNav(history); 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(() => { useEffect(() => {
const { current: wrapperElement } = appRef; const { current: wrapperElement } = appRef;
@ -212,13 +163,9 @@ function App(props: Props) {
ReactModal.setAppElement(wrapperElement); ReactModal.setAppElement(wrapperElement);
} }
fetchAccessToken();
// @if TARGET='app'
fetchChannelListMine(); // This is fetched after a user is signed in on web fetchChannelListMine(); // This is fetched after a user is signed in on web
fetchCollectionListMine(); fetchCollectionListMine();
// @endif }, [appRef, fetchChannelListMine, fetchCollectionListMine]);
}, [appRef, fetchAccessToken, fetchChannelListMine, fetchCollectionListMine]);
useEffect(() => { useEffect(() => {
// $FlowFixMe // $FlowFixMe
@ -261,71 +208,20 @@ function App(props: Props) {
}, [shouldMigrateLanguage, setLanguage]); }, [shouldMigrateLanguage, setLanguage]);
useEffect(() => { useEffect(() => {
// Check that previousHasVerifiedEmail was not undefined instead of just not truthy if (updatePreferences && getWalletSyncPref) {
// This ensures we don't fire the emailVerified event on the initial user fetch getWalletSyncPref().then(() => updatePreferences());
if (previousHasVerifiedEmail === false && hasVerifiedEmail) {
analytics.emailVerifiedEvent();
} }
}, [previousHasVerifiedEmail, hasVerifiedEmail, signIn]); }, [updatePreferences, getWalletSyncPref]);
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]);
// batch resolve subscriptions to be used by the sideNavigation component. // batch resolve subscriptions to be used by the sideNavigation component.
// add it here so that it only resolves the first time, despite route changes. // 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 because it has to be executed before the sideNavigation component requests them
useLayoutEffect(() => { useLayoutEffect(() => {
if (sidebarOpen && isPersonalized && subscriptions && !resolvedSubscriptions) { if (sidebarOpen && subscriptions && !resolvedSubscriptions) {
setResolvedSubscriptions(true); setResolvedSubscriptions(true);
resolveUris(subscriptions.map((sub) => sub.uri)); resolveUris(subscriptions.map((sub) => sub.uri));
} }
}, [sidebarOpen, isPersonalized, resolvedSubscriptions, subscriptions, resolveUris, setResolvedSubscriptions]); }, [sidebarOpen, resolvedSubscriptions, subscriptions, resolveUris, setResolvedSubscriptions]);
useEffect(() => { useEffect(() => {
// When language is changed or translations are fetched, we render. // When language is changed or translations are fetched, we render.

View file

@ -1,12 +1,9 @@
import Button from './view'; import Button from './view';
import React, { forwardRef } from 'react'; import React, { forwardRef } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
const mapStateToProps = (state) => ({ const mapStateToProps = (state) => ({
pathname: state.router.location.pathname, pathname: state.router.location.pathname,
emailVerified: selectUserVerifiedEmail(state),
user: selectUser(state),
}); });
const ConnectedButton = connect(mapStateToProps)(Button); const ConnectedButton = connect(mapStateToProps)(Button);

View file

@ -32,11 +32,9 @@ type Props = {
onMouseEnter: ?(any) => any, onMouseEnter: ?(any) => any,
onMouseLeave: ?(any) => any, onMouseLeave: ?(any) => any,
pathname: string, pathname: string,
emailVerified: boolean,
myref: any, myref: any,
dispatch: any, dispatch: any,
'aria-label'?: string, 'aria-label'?: string,
user: ?User,
}; };
// use forwardRef to allow consumers to pass refs to the button content if they want to // 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, iconSize,
iconColor, iconColor,
activeClass, activeClass,
emailVerified,
myref, myref,
dispatch, // <button> doesn't know what to do with dispatch dispatch, // <button> doesn't know what to do with dispatch
pathname, pathname,
user,
authSrc, authSrc,
...otherProps ...otherProps
} = props; } = props;

View file

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

View file

@ -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 */

View file

@ -11,7 +11,6 @@ import { doResolveUris } from 'redux/actions/claims';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked'; import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { makeSelectClientSetting, selectShowMatureContent } from 'redux/selectors/settings'; import { makeSelectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
import ChannelContent from './view'; import ChannelContent from './view';
@ -29,7 +28,6 @@ const select = (state, props) => {
channelIsMine: selectClaimIsMine(state, claim), channelIsMine: selectClaimIsMine(state, claim),
channelIsBlocked: makeSelectChannelIsMuted(props.uri)(state), channelIsBlocked: makeSelectChannelIsMuted(props.uri)(state),
claim, claim,
isAuthenticated: selectUserVerifiedEmail(state),
showMature: selectShowMatureContent(state), showMature: selectShowMatureContent(state),
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state), tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
}; };

View file

@ -26,7 +26,6 @@ type Props = {
defaultPageSize?: number, defaultPageSize?: number,
defaultInfiniteScroll?: Boolean, defaultInfiniteScroll?: Boolean,
claim: Claim, claim: Claim,
isAuthenticated: boolean,
showMature: boolean, showMature: boolean,
tileLayout: boolean, tileLayout: boolean,
viewHiddenChannels: boolean, viewHiddenChannels: boolean,

View file

@ -15,8 +15,6 @@ import { selectBalance } from 'redux/selectors/wallet';
import { doUpdateChannel, doCreateChannel, doClearChannelErrors } from 'redux/actions/claims'; import { doUpdateChannel, doCreateChannel, doClearChannelErrors } from 'redux/actions/claims';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
import { doUpdateBlockListForPublishedChannel } from 'redux/actions/comments'; import { doUpdateBlockListForPublishedChannel } from 'redux/actions/comments';
import { doClaimInitialRewards } from 'redux/actions/rewards';
import { selectIsClaimingInitialRewards, selectHasClaimedInitialRewards } from 'redux/selectors/rewards';
import ChannelForm from './view'; import ChannelForm from './view';
const select = (state, props) => ({ const select = (state, props) => ({
@ -36,8 +34,6 @@ const select = (state, props) => ({
createError: selectCreateChannelError(state), createError: selectCreateChannelError(state),
creatingChannel: selectCreatingChannel(state), creatingChannel: selectCreatingChannel(state),
balance: selectBalance(state), balance: selectBalance(state),
isClaimingInitialRewards: selectIsClaimingInitialRewards(state),
hasClaimedInitialRewards: selectHasClaimedInitialRewards(state),
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({
@ -52,7 +48,6 @@ const perform = (dispatch) => ({
); );
}, },
clearChannelErrors: () => dispatch(doClearChannelErrors()), clearChannelErrors: () => dispatch(doClearChannelErrors()),
claimInitialRewards: () => dispatch(doClaimInitialRewards()),
}); });
export default connect(select, perform)(ChannelForm); export default connect(select, perform)(ChannelForm);

View file

@ -51,7 +51,6 @@ type Props = {
createError: string, createError: string,
creatingChannel: boolean, creatingChannel: boolean,
clearChannelErrors: () => void, clearChannelErrors: () => void,
claimInitialRewards: () => void,
onDone: () => void, onDone: () => void,
openModal: ( openModal: (
id: string, id: string,
@ -59,8 +58,6 @@ type Props = {
) => void, ) => void,
uri: string, uri: string,
disabled: boolean, disabled: boolean,
isClaimingInitialRewards: boolean,
hasClaimedInitialRewards: boolean,
}; };
function ChannelForm(props: Props) { function ChannelForm(props: Props) {
@ -85,11 +82,8 @@ function ChannelForm(props: Props) {
creatingChannel, creatingChannel,
createError, createError,
clearChannelErrors, clearChannelErrors,
claimInitialRewards,
openModal, openModal,
disabled, disabled,
isClaimingInitialRewards,
hasClaimedInitialRewards,
} = props; } = props;
const [nameError, setNameError] = React.useState(undefined); const [nameError, setNameError] = React.useState(undefined);
const [bidError, setBidError] = React.useState(''); const [bidError, setBidError] = React.useState('');
@ -107,21 +101,11 @@ function ChannelForm(props: Props) {
const primaryLanguage = Array.isArray(languageParam) && languageParam.length && languageParam[0]; const primaryLanguage = Array.isArray(languageParam) && languageParam.length && languageParam[0];
const secondaryLanguage = Array.isArray(languageParam) && languageParam.length >= 2 && languageParam[1]; const secondaryLanguage = Array.isArray(languageParam) && languageParam.length >= 2 && languageParam[1];
const submitLabel = React.useMemo(() => { const submitLabel = React.useMemo(() => {
if (isClaimingInitialRewards) {
return __('Claiming credits...');
}
return creatingChannel || updatingChannel ? __('Submitting...') : __('Submit'); return creatingChannel || updatingChannel ? __('Submitting...') : __('Submit');
}, [isClaimingInitialRewards, creatingChannel, updatingChannel]); }, [creatingChannel, updatingChannel]);
const submitDisabled = React.useMemo(() => { const submitDisabled = React.useMemo(() => {
return ( return creatingChannel || updatingChannel || coverError || bidError || (isNewChannel && !params.name);
isClaimingInitialRewards || }, [creatingChannel, updatingChannel, nameError, bidError, isNewChannel, params.name]);
creatingChannel ||
updatingChannel ||
coverError ||
bidError ||
(isNewChannel && !params.name)
);
}, [isClaimingInitialRewards, creatingChannel, updatingChannel, nameError, bidError, isNewChannel, params.name]);
function getChannelParams() { function getChannelParams() {
// fill this in with sdk data // fill this in with sdk data
@ -255,12 +239,6 @@ function ChannelForm(props: Props) {
clearChannelErrors(); clearChannelErrors();
}, [clearChannelErrors]); }, [clearChannelErrors]);
React.useEffect(() => {
if (!hasClaimedInitialRewards) {
claimInitialRewards();
}
}, [hasClaimedInitialRewards, claimInitialRewards]);
const coverSrc = coverError ? ThumbnailBrokenImage : coverPreview; const coverSrc = coverError ? ThumbnailBrokenImage : coverPreview;
let thumbnailPreview; let thumbnailPreview;

View file

@ -10,7 +10,6 @@ import { doCommentUpdate, doCommentList } from 'redux/actions/comments';
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked'; import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import { doToast } from 'redux/actions/notifications'; import { doToast } from 'redux/actions/notifications';
import { doClearPlayingUri } from 'redux/actions/content'; import { doClearPlayingUri } from 'redux/actions/content';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { import {
selectLinkedCommentAncestors, selectLinkedCommentAncestors,
selectOthersReactsForComment, selectOthersReactsForComment,
@ -33,7 +32,6 @@ const select = (state, props) => {
claim: makeSelectClaimForUri(uri)(state), claim: makeSelectClaimForUri(uri)(state),
thumbnail: author_uri && selectThumbnailForUri(state, author_uri), thumbnail: author_uri && selectThumbnailForUri(state, author_uri),
channelIsBlocked: author_uri && makeSelectChannelIsMuted(author_uri)(state), channelIsBlocked: author_uri && makeSelectChannelIsMuted(author_uri)(state),
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
othersReacts: selectOthersReactsForComment(state, reactionKey), othersReacts: selectOthersReactsForComment(state, reactionKey),
activeChannelClaim, activeChannelClaim,
hasChannels: selectHasChannels(state), hasChannels: selectHasChannels(state),

View file

@ -49,7 +49,6 @@ type Props = {
linkedCommentId?: string, linkedCommentId?: string,
linkedCommentAncestors: { [string]: Array<string> }, linkedCommentAncestors: { [string]: Array<string> },
hasChannels: boolean, hasChannels: boolean,
commentingEnabled: boolean,
doToast: ({ message: string }) => void, doToast: ({ message: string }) => void,
isTopLevel?: boolean, isTopLevel?: boolean,
threadDepth: number, threadDepth: number,
@ -82,7 +81,6 @@ function CommentView(props: Props) {
totalReplyPages, totalReplyPages,
linkedCommentId, linkedCommentId,
linkedCommentAncestors, linkedCommentAncestors,
commentingEnabled,
hasChannels, hasChannels,
doToast, doToast,
isTopLevel, isTopLevel,
@ -368,7 +366,7 @@ function CommentView(props: Props) {
<div className="comment__actions"> <div className="comment__actions">
{threadDepth !== 0 && ( {threadDepth !== 0 && (
<Button <Button
label={commentingEnabled ? __('Reply') : __('Log in to reply')} label={__('Reply')}
className="comment__action" className="comment__action"
onClick={handleCommentReply} onClick={handleCommentReply}
icon={ICONS.REPLY} icon={ICONS.REPLY}

View file

@ -157,31 +157,6 @@ export default function CreatorAnalytics(props: Props) {
/> />
</div> </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 ? ( {stats.VideoURITopNew ? (
<Card <Card
className="section" className="section"

View file

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

View file

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

View file

@ -1,7 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeSelectClaimForUri, makeSelectContentTypeForUri, makeSelectMetadataForUri } from 'redux/selectors/claims'; import { makeSelectClaimForUri, makeSelectContentTypeForUri, makeSelectMetadataForUri } from 'redux/selectors/claims';
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info'; import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
import { selectUser } from 'redux/selectors/user';
import { doOpenFileInFolder } from 'redux/actions/file'; import { doOpenFileInFolder } from 'redux/actions/file';
import FileDetails from './view'; import FileDetails from './view';
@ -10,7 +9,6 @@ const select = (state, props) => ({
contentType: makeSelectContentTypeForUri(props.uri)(state), contentType: makeSelectContentTypeForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state), fileInfo: makeSelectFileInfoForUri(props.uri)(state),
metadata: makeSelectMetadataForUri(props.uri)(state), metadata: makeSelectMetadataForUri(props.uri)(state),
user: selectUser(state),
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({

View file

@ -10,7 +10,6 @@ type Props = {
metadata: StreamMetadata, metadata: StreamMetadata,
openFolder: (string) => void, openFolder: (string) => void,
contentType: string, contentType: string,
user: ?any,
}; };
class FileDetails extends PureComponent<Props> { class FileDetails extends PureComponent<Props> {

View file

@ -1,7 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeSelectFileInfoForUri, makeSelectStreamingUrlForUri } from 'redux/selectors/file_info'; import { makeSelectFileInfoForUri, makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
import { makeSelectClaimWasPurchased } from 'redux/selectors/claims'; import { makeSelectClaimWasPurchased } from 'redux/selectors/claims';
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
import { makeSelectFileRenderModeForUri, selectPrimaryUri } from 'redux/selectors/content'; import { makeSelectFileRenderModeForUri, selectPrimaryUri } from 'redux/selectors/content';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { doAnalyticsView } from 'redux/actions/app'; import { doAnalyticsView } from 'redux/actions/app';
@ -19,7 +18,6 @@ const select = (state, props) => ({
const perform = (dispatch) => ({ const perform = (dispatch) => ({
triggerAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)), triggerAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
}); });
export default withRouter(connect(select, perform)(FileRenderInline)); export default withRouter(connect(select, perform)(FileRenderInline));

View file

@ -11,23 +11,13 @@ type Props = {
renderMode: string, renderMode: string,
streamingUrl?: string, streamingUrl?: string,
triggerAnalyticsView: (string, number) => Promise<any>, triggerAnalyticsView: (string, number) => Promise<any>,
claimRewards: () => void,
costInfo: any, costInfo: any,
claimWasPurchased: boolean, claimWasPurchased: boolean,
}; };
export default function FileRenderInline(props: Props) { export default function FileRenderInline(props: Props) {
const { const { isPlaying, fileInfo, uri, streamingUrl, triggerAnalyticsView, renderMode, costInfo, claimWasPurchased } =
isPlaying, props;
fileInfo,
uri,
streamingUrl,
triggerAnalyticsView,
claimRewards,
renderMode,
costInfo,
claimWasPurchased,
} = props;
const [playTime, setPlayTime] = useState(); const [playTime, setPlayTime] = useState();
const isFree = !costInfo || (costInfo.cost !== undefined && costInfo.cost === 0); const isFree = !costInfo || (costInfo.cost !== undefined && costInfo.cost === 0);
const isReadyToView = fileInfo && fileInfo.completed; const isReadyToView = fileInfo && fileInfo.completed;
@ -55,11 +45,10 @@ export default function FileRenderInline(props: Props) {
const timeToStart = Date.now() - playTime; const timeToStart = Date.now() - playTime;
triggerAnalyticsView(uri, timeToStart).then(() => { triggerAnalyticsView(uri, timeToStart).then(() => {
claimRewards();
setPlayTime(null); setPlayTime(null);
}); });
} }
}, [setPlayTime, claimRewards, triggerAnalyticsView, isReadyToPlay, playTime, uri]); }, [setPlayTime, triggerAnalyticsView, isReadyToPlay, playTime, uri]);
if (!isPlaying) { if (!isPlaying) {
return null; return null;

View file

@ -1,29 +1,25 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
import { doSignOut } from 'redux/actions/app'; import { doSignOut } from 'redux/actions/app';
import { formatCredits } from 'util/format-credits'; import { formatCredits } from 'util/format-credits';
import { selectClientSetting } from 'redux/selectors/settings'; import { selectClientSetting } from 'redux/selectors/settings';
import { selectGetSyncErrorMessage } from 'redux/selectors/sync'; import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { selectHasNavigated } from 'redux/selectors/app'; import { selectHasNavigated } from 'redux/selectors/app';
import { selectTotalBalance, selectBalance } from 'redux/selectors/wallet'; import { selectTotalBalance, selectBalance } from 'redux/selectors/wallet';
import { selectEmailToVerify, selectUser } from 'redux/selectors/user';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { doLbrysyncRegister } from 'redux/actions/sync';
import Header from './view'; import Header from './view';
const select = (state) => ({ const select = (state) => ({
balance: selectBalance(state), balance: selectBalance(state),
emailToVerify: selectEmailToVerify(state),
hasNavigated: selectHasNavigated(state), hasNavigated: selectHasNavigated(state),
hideBalance: selectClientSetting(state, SETTINGS.HIDE_BALANCE), hideBalance: selectClientSetting(state, SETTINGS.HIDE_BALANCE),
roundedBalance: formatCredits(selectTotalBalance(state), 2, true), roundedBalance: formatCredits(selectTotalBalance(state), 2, true),
roundedSpendableBalance: formatCredits(selectBalance(state), 2, true), roundedSpendableBalance: formatCredits(selectBalance(state), 2, true),
syncError: selectGetSyncErrorMessage(state), syncError: selectGetSyncErrorMessage(state),
user: selectUser(state),
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({
clearEmailEntry: () => dispatch(doClearEmailEntry()), lbrysyncRegister: (username, password) => dispatch(doLbrysyncRegister(username, password)),
clearPasswordEntry: () => dispatch(doClearPasswordEntry()),
signOut: () => dispatch(doSignOut()), signOut: () => dispatch(doSignOut()),
}); });

View file

@ -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 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, balance: number,
emailToVerify?: string,
hasNavigated: boolean, hasNavigated: boolean,
hideBalance: boolean, hideBalance: boolean,
hideCancel: boolean, hideCancel: boolean,
@ -43,8 +42,6 @@ type Props = {
roundedSpendableBalance: string, roundedSpendableBalance: string,
sidebarOpen: boolean, sidebarOpen: boolean,
syncError: ?string, syncError: ?string,
clearEmailEntry: () => void,
clearPasswordEntry: () => void,
setSidebarOpen: (boolean) => void, setSidebarOpen: (boolean) => void,
signOut: () => void, signOut: () => void,
}; };
@ -54,7 +51,6 @@ const Header = (props: Props) => {
authHeader, authHeader,
backout, backout,
balance, balance,
emailToVerify,
hideBalance, hideBalance,
hideCancel, hideCancel,
history, history,
@ -63,8 +59,6 @@ const Header = (props: Props) => {
roundedSpendableBalance, roundedSpendableBalance,
sidebarOpen, sidebarOpen,
syncError, syncError,
clearEmailEntry,
clearPasswordEntry,
setSidebarOpen, setSidebarOpen,
signOut, signOut,
} = props; } = 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 // 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 isVerifyPage = pathname.includes(PAGES.AUTH_VERIFY);
const isSignUpPage = pathname.includes(PAGES.AUTH); const isSignUpPage = pathname.includes(PAGES.AUTH);
const isSignInPage = pathname.includes(PAGES.AUTH_SIGNIN);
const isPwdResetPage = pathname.includes(PAGES.AUTH_PASSWORD_RESET); const isPwdResetPage = pathname.includes(PAGES.AUTH_PASSWORD_RESET);
// For pages that allow for "backing out", shows a backout option instead of the Home logo // 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" // className="button--header-close"
icon={ICONS.REMOVE} icon={ICONS.REMOVE}
onClick={() => { onClick={() => {
clearEmailEntry();
clearPasswordEntry();
if (syncError) signOut(); if (syncError) signOut();
if ((isSignInPage && !emailToVerify) || isSignUpPage || isPwdResetPage) { if (isSignUpPage || isPwdResetPage) {
goBack(); goBack();
} else { } else {
push('/'); push('/');

View file

@ -4,13 +4,11 @@ import { selectActiveChannelStakedLevel } from 'redux/selectors/app';
import { selectClientSetting } from 'redux/selectors/settings'; import { selectClientSetting } from 'redux/selectors/settings';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import HeaderMenuButtons from './view'; import HeaderMenuButtons from './view';
import { selectUser } from 'redux/selectors/user';
const select = (state) => ({ const select = (state) => ({
activeChannelStakedLevel: selectActiveChannelStakedLevel(state), activeChannelStakedLevel: selectActiveChannelStakedLevel(state),
automaticDarkModeEnabled: selectClientSetting(state, SETTINGS.AUTOMATIC_DARK_MODE_ENABLED), automaticDarkModeEnabled: selectClientSetting(state, SETTINGS.AUTOMATIC_DARK_MODE_ENABLED),
currentTheme: selectClientSetting(state, SETTINGS.THEME), currentTheme: selectClientSetting(state, SETTINGS.THEME),
user: selectUser(state),
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({

View file

@ -14,14 +14,13 @@ import Tooltip from 'component/common/tooltip';
type HeaderMenuButtonProps = { type HeaderMenuButtonProps = {
automaticDarkModeEnabled: boolean, automaticDarkModeEnabled: boolean,
currentTheme: string, currentTheme: string,
user: ?User,
handleThemeToggle: (boolean, string) => void, handleThemeToggle: (boolean, string) => void,
}; };
export default function HeaderMenuButtons(props: HeaderMenuButtonProps) { 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 ( return (
<div className="header__buttons"> <div className="header__buttons">
@ -35,6 +34,7 @@ export default function HeaderMenuButtons(props: HeaderMenuButtonProps) {
<MenuList className="menu__list--header"> <MenuList className="menu__list--header">
<HeaderMenuLink page={PAGES.UPLOAD} icon={ICONS.PUBLISH} name={__('Upload')} /> <HeaderMenuLink page={PAGES.UPLOAD} icon={ICONS.PUBLISH} name={__('Upload')} />
<HeaderMenuLink page={PAGES.CHANNEL_NEW} icon={ICONS.CHANNEL} name={__('New Channel')} /> <HeaderMenuLink page={PAGES.CHANNEL_NEW} icon={ICONS.CHANNEL} name={__('New Channel')} />
<HeaderMenuLink page={PAGES.SETTINGS_SYNC} icon={ICONS.GAMING} name={__('Sign In')} />
</MenuList> </MenuList>
</Menu> </Menu>

View file

@ -1,12 +1,10 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUnseenNotificationCount } from 'redux/selectors/notifications'; import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
import { doSeeAllNotifications } from 'redux/actions/notifications'; import { doSeeAllNotifications } from 'redux/actions/notifications';
import { selectUser } from 'redux/selectors/user';
import NotificationHeaderButton from './view'; import NotificationHeaderButton from './view';
const select = (state) => ({ const select = (state) => ({
unseenCount: selectUnseenNotificationCount(state), unseenCount: selectUnseenNotificationCount(state),
user: selectUser(state),
}); });
export default connect(select, { export default connect(select, {

View file

@ -13,15 +13,14 @@ import Tooltip from 'component/common/tooltip';
type Props = { type Props = {
unseenCount: number, unseenCount: number,
user: ?User,
doSeeAllNotifications: () => void, doSeeAllNotifications: () => void,
}; };
export default function NotificationHeaderButton(props: Props) { export default function NotificationHeaderButton(props: Props) {
const { unseenCount, user, doSeeAllNotifications } = props; const { unseenCount, doSeeAllNotifications } = props;
const { push } = useHistory(); const { push } = useHistory();
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui); const notificationsEnabled = ENABLE_UI_NOTIFICATIONS;
function handleMenuClick() { function handleMenuClick() {
if (unseenCount > 0) doSeeAllNotifications(); if (unseenCount > 0) doSeeAllNotifications();

View file

@ -1,18 +1,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doOpenModal } from 'redux/actions/app';
import { selectActiveChannelClaim } from 'redux/selectors/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'; import HeaderProfileMenuButton from './view';
const select = (state) => ({ const select = (state) => ({
activeChannelClaim: selectActiveChannelClaim(state), activeChannelClaim: selectActiveChannelClaim(state),
email: selectUserEmail(state),
authenticated: selectUserVerifiedEmail(state),
}); });
const perform = (dispatch) => ({ export default connect(select)(HeaderProfileMenuButton);
openSignOutModal: () => dispatch(doOpenModal(MODALS.SIGN_OUT)),
});
export default connect(select, perform)(HeaderProfileMenuButton);

View file

@ -1,6 +1,6 @@
// @flow // @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 ICONS from 'constants/icons';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import ChannelThumbnail from 'component/channelThumbnail'; import ChannelThumbnail from 'component/channelThumbnail';
@ -11,13 +11,10 @@ import React from 'react';
type HeaderMenuButtonProps = { type HeaderMenuButtonProps = {
activeChannelClaim: ?ChannelClaim, activeChannelClaim: ?ChannelClaim,
email: ?string,
authenticated: boolean,
openSignOutModal: () => void,
}; };
export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) { export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) {
const { activeChannelClaim, email, openSignOutModal, authenticated } = props; const { activeChannelClaim } = props;
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url; const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
@ -43,7 +40,7 @@ export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) {
<HeaderMenuLink page={PAGES.UPLOADS} icon={ICONS.PUBLISH} name={__('Uploads')} /> <HeaderMenuLink page={PAGES.UPLOADS} icon={ICONS.PUBLISH} name={__('Uploads')} />
<HeaderMenuLink page={PAGES.CHANNELS} icon={ICONS.CHANNEL} name={__('Channels')} /> <HeaderMenuLink page={PAGES.CHANNELS} icon={ICONS.CHANNEL} name={__('Channels')} />
<HeaderMenuLink page={PAGES.CREATOR_DASHBOARD} icon={ICONS.ANALYTICS} name={__('Creator Analytics')} /> <HeaderMenuLink page={PAGES.CREATOR_DASHBOARD} icon={ICONS.ANALYTICS} name={__('Creator Analytics')} />
{/* No sync button for now
{authenticated ? ( {authenticated ? (
<MenuItem onSelect={openSignOutModal}> <MenuItem onSelect={openSignOutModal}>
<div className="menu__link"> <div className="menu__link">
@ -53,8 +50,9 @@ export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) {
<span className="menu__link-help">{email}</span> <span className="menu__link-help">{email}</span>
</MenuItem> </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> </MenuList>
</Menu> </Menu>
</div> </div>

View file

@ -1,11 +1,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUnseenNotificationCount } from 'redux/selectors/notifications'; import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
import { selectUser } from 'redux/selectors/user';
import NotificationHeaderButton from './view'; import NotificationHeaderButton from './view';
const select = (state) => ({ const select = (state) => ({
unseenCount: selectUnseenNotificationCount(state), unseenCount: selectUnseenNotificationCount(state),
user: selectUser(state),
}); });
export default connect(select)(NotificationHeaderButton); export default connect(select)(NotificationHeaderButton);

View file

@ -6,12 +6,11 @@ import { ENABLE_UI_NOTIFICATIONS } from 'config';
type Props = { type Props = {
unseenCount: number, unseenCount: number,
inline: boolean, inline: boolean,
user: ?User,
}; };
export default function NotificationHeaderButton(props: Props) { export default function NotificationHeaderButton(props: Props) {
const { unseenCount, inline = false, user } = props; const { unseenCount, inline = false } = props;
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui); const notificationsEnabled = ENABLE_UI_NOTIFICATIONS;
if (unseenCount === 0 || !notificationsEnabled) { if (unseenCount === 0 || !notificationsEnabled) {
return null; return null;

View file

@ -1,9 +1,3 @@
import { connect } from 'react-redux';
import { selectUser } from 'redux/selectors/user';
import NudgeFloating from './view'; import NudgeFloating from './view';
const select = state => ({ export default NudgeFloating;
user: selectUser(state),
});
export default connect(select)(NudgeFloating);

View file

@ -5,22 +5,20 @@ import usePersistedState from 'effects/use-persisted-state';
import Button from 'component/button'; import Button from 'component/button';
type Props = { type Props = {
user: ?User,
name: string, name: string,
text: string, text: string,
}; };
export default function NudgeFloating(props: Props) { export default function NudgeFloating(props: Props) {
const { user, name, text } = props; const { name, text } = props;
const [showNudge, setShowNudge] = React.useState(false); const [showNudge, setShowNudge] = React.useState(false);
const [nudgeAcknowledged, setNudgeAcknowledged] = usePersistedState(name, false); const [nudgeAcknowledged, setNudgeAcknowledged] = usePersistedState(name, false);
const emailVerified = user && user.has_verified_email;
React.useEffect(() => { React.useEffect(() => {
if (!emailVerified && !nudgeAcknowledged) { if (!nudgeAcknowledged) {
setShowNudge(true); setShowNudge(true);
} }
}, [emailVerified, nudgeAcknowledged]); }, [nudgeAcknowledged]);
return ( return (
showNudge && ( showNudge && (

View file

@ -1,23 +1,11 @@
import { DOMAIN } from 'config';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doSetDaemonSetting } from 'redux/actions/settings'; import { doSetDaemonSetting } from 'redux/actions/settings';
import { doSignOut } from 'redux/actions/app';
import * as DAEMON_SETTINGS from 'constants/daemon_settings'; 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'; import PrivacyAgreement from './view';
const select = (state) => ({
authenticated: selectUserVerifiedEmail(state),
});
const perform = (dispatch) => ({ const perform = (dispatch) => ({
setShareDataInternal: (share) => dispatch(doSetDaemonSetting(DAEMON_SETTINGS.SHARE_USAGE_DATA, share)), 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);

View file

@ -1,7 +1,6 @@
// @flow // @flow
import React, { useState } from 'react'; import React, { useState } from 'react';
import Button from 'component/button'; import Button from 'component/button';
import I18nMessage from 'component/i18nMessage';
import { FormField } from 'component/common/form-components/form-field'; import { FormField } from 'component/common/form-components/form-field';
import { Form } from 'component/common/form-components/form'; import { Form } from 'component/common/form-components/form';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
@ -14,13 +13,11 @@ const NONE = 'none';
type Props = { type Props = {
signOut: () => void, signOut: () => void,
setShareDataInternal: (boolean) => void, setShareDataInternal: (boolean) => void,
authenticated: boolean,
authenticateIfSharingData: () => void,
handleNextPage: () => void, handleNextPage: () => void,
}; };
function PrivacyAgreement(props: Props) { function PrivacyAgreement(props: Props) {
const { setShareDataInternal, authenticated, signOut, authenticateIfSharingData, handleNextPage } = props; const { setShareDataInternal, handleNextPage } = props;
const [share, setShare] = useState(undefined); // preload const [share, setShare] = useState(undefined); // preload
function handleSubmit() { function handleSubmit() {
@ -30,10 +27,6 @@ function PrivacyAgreement(props: Props) {
setShareDataInternal(false); setShareDataInternal(false);
} }
if (share === LIMITED) {
authenticateIfSharingData();
}
handleNextPage(); handleNextPage();
} }
@ -63,7 +56,6 @@ function PrivacyAgreement(props: Props) {
onChange={(e) => setShare(LIMITED)} onChange={(e) => setShare(LIMITED)}
/> />
<FormField <FormField
disabled={authenticated}
name={'shareNot'} name={'shareNot'}
type="radio" type="radio"
checked={share === NONE} checked={share === NONE}
@ -77,19 +69,6 @@ function PrivacyAgreement(props: Props) {
)} )}
onChange={(e) => setShare(NONE)} 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> </fieldset>
<div className={'card__actions'}> <div className={'card__actions'}>
<Button button="primary" label={__(`Next`)} disabled={!share} type="submit" /> <Button button="primary" label={__(`Next`)} disabled={!share} type="submit" />

View file

@ -2,18 +2,13 @@ import { connect } from 'react-redux';
import { selectPublishFormValues } from 'redux/selectors/publish'; import { selectPublishFormValues } from 'redux/selectors/publish';
import { doUpdatePublishForm } from 'redux/actions/publish'; import { doUpdatePublishForm } from 'redux/actions/publish';
import PublishAdditionalOptions from './view'; import PublishAdditionalOptions from './view';
import { selectUser, selectAccessToken } from 'redux/selectors/user';
import { doFetchAccessToken } from 'redux/actions/user';
const select = (state) => ({ const select = (state) => ({
...selectPublishFormValues(state), ...selectPublishFormValues(state),
accessToken: selectAccessToken(state),
user: selectUser(state),
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({
updatePublishForm: (value) => dispatch(doUpdatePublishForm(value)), updatePublishForm: (value) => dispatch(doUpdatePublishForm(value)),
fetchAccessToken: () => dispatch(doFetchAccessToken()),
}); });
export default connect(select, perform)(PublishAdditionalOptions); export default connect(select, perform)(PublishAdditionalOptions);

View file

@ -10,14 +10,7 @@ import Card from 'component/common/card';
import SUPPORTED_LANGUAGES from 'constants/supported_languages'; import SUPPORTED_LANGUAGES from 'constants/supported_languages';
import { sortLanguageMap } from 'util/default-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 = { type Props = {
user: ?User,
language: ?string, language: ?string,
name: ?string, name: ?string,
licenseType: ?string, licenseType: ?string,
@ -25,92 +18,16 @@ type Props = {
licenseUrl: ?string, licenseUrl: ?string,
disabled: boolean, disabled: boolean,
updatePublishForm: ({}) => void, updatePublishForm: ({}) => void,
useLBRYUploader: boolean,
needsYTAuth: boolean,
fetchAccessToken: () => void,
accessToken: string,
}; };
function PublishAdditionalOptions(props: Props) { function PublishAdditionalOptions(props: Props) {
const { const { language, name, licenseType, otherLicenseDescription, licenseUrl, updatePublishForm } = props;
language,
name,
licenseType,
otherLicenseDescription,
licenseUrl,
updatePublishForm,
// user,
// useLBRYUploader,
// needsYTAuth,
// accessToken,
// fetchAccessToken,
} = props;
const [hideSection, setHideSection] = usePersistedState('publish-advanced-options', true); 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() { function toggleHideSection() {
setHideSection(!hideSection); 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 ( return (
<Card <Card
className="card--enable-overflow" className="card--enable-overflow"
@ -118,41 +35,6 @@ function PublishAdditionalOptions(props: Props) {
<React.Fragment> <React.Fragment>
{!hideSection && ( {!hideSection && (
<div className={classnames({ 'card--disabled': !name })}> <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"> <div className="section">
<PublishReleaseDate /> <PublishReleaseDate />

View file

@ -17,12 +17,6 @@ import {
} from 'redux/selectors/publish'; } from 'redux/selectors/publish';
import * as RENDER_MODES from 'constants/file_render_modes'; import * as RENDER_MODES from 'constants/file_render_modes';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { doClaimInitialRewards } from 'redux/actions/rewards';
import {
selectUnclaimedRewardValue,
selectIsClaimingInitialRewards,
selectHasClaimedInitialRewards,
} from 'redux/selectors/rewards';
import { import {
selectModal, selectModal,
selectActiveChannelClaim, selectActiveChannelClaim,
@ -31,7 +25,6 @@ import {
} from 'redux/selectors/app'; } from 'redux/selectors/app';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { makeSelectFileRenderModeForUri } from 'redux/selectors/content'; import { makeSelectFileRenderModeForUri } from 'redux/selectors/content';
import { selectUser } from 'redux/selectors/user';
import PublishForm from './view'; import PublishForm from './view';
const select = (state) => { const select = (state) => {
@ -41,7 +34,6 @@ const select = (state) => {
return { return {
...selectPublishFormValues(state), ...selectPublishFormValues(state),
user: selectUser(state),
// The winning claim for a short lbry uri // The winning claim for a short lbry uri
amountNeededForTakeover: selectTakeOverAmount(state), amountNeededForTakeover: selectTakeOverAmount(state),
isPostClaim, isPostClaim,
@ -55,14 +47,11 @@ const select = (state) => {
remoteUrl: makeSelectPublishFormValue('remoteFileUrl')(state), remoteUrl: makeSelectPublishFormValue('remoteFileUrl')(state),
publishSuccess: makeSelectPublishFormValue('publishSuccess')(state), publishSuccess: makeSelectPublishFormValue('publishSuccess')(state),
isResolvingUri: selectIsResolvingPublishUris(state), isResolvingUri: selectIsResolvingPublishUris(state),
totalRewardValue: selectUnclaimedRewardValue(state),
modal: selectModal(state), modal: selectModal(state),
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state), enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
activeChannelClaim: selectActiveChannelClaim(state), activeChannelClaim: selectActiveChannelClaim(state),
incognito: selectIncognito(state), incognito: selectIncognito(state),
activeChannelStakedLevel: selectActiveChannelStakedLevel(state), activeChannelStakedLevel: selectActiveChannelStakedLevel(state),
isClaimingInitialRewards: selectIsClaimingInitialRewards(state),
hasClaimedInitialRewards: selectHasClaimedInitialRewards(state),
}; };
}; };
@ -74,7 +63,6 @@ const perform = (dispatch) => ({
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)), prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()), resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
checkAvailability: (name) => dispatch(doCheckPublishNameAvailability(name)), checkAvailability: (name) => dispatch(doCheckPublishNameAvailability(name)),
claimInitialRewards: () => dispatch(doClaimInitialRewards()),
}); });
export default connect(select, perform)(PublishForm); export default connect(select, perform)(PublishForm);

View file

@ -62,7 +62,6 @@ type Props = {
licenseType: string, licenseType: string,
otherLicenseDescription: ?string, otherLicenseDescription: ?string,
licenseUrl: ?string, licenseUrl: ?string,
useLBRYUploader: ?boolean,
publishing: boolean, publishing: boolean,
publishSuccess: boolean, publishSuccess: boolean,
balance: number, balance: number,
@ -76,19 +75,14 @@ type Props = {
// Add back type // Add back type
updatePublishForm: (any) => void, updatePublishForm: (any) => void,
checkAvailability: (string) => void, checkAvailability: (string) => void,
ytSignupPending: boolean,
modal: { id: string, modalProps: {} }, modal: { id: string, modalProps: {} },
enablePublishPreview: boolean, enablePublishPreview: boolean,
activeChannelClaim: ?ChannelClaim, activeChannelClaim: ?ChannelClaim,
incognito: boolean, incognito: boolean,
user: ?User,
activeChannelStakedLevel: number, activeChannelStakedLevel: number,
isPostClaim: boolean, isPostClaim: boolean,
permanentUrl: ?string, permanentUrl: ?string,
remoteUrl: ?string, remoteUrl: ?string,
isClaimingInitialRewards: boolean,
claimInitialRewards: () => void,
hasClaimedInitialRewards: boolean,
}; };
function PublishForm(props: Props) { function PublishForm(props: Props) {
@ -116,7 +110,6 @@ function PublishForm(props: Props) {
publish, publish,
disabled = false, disabled = false,
checkAvailability, checkAvailability,
ytSignupPending,
modal, modal,
enablePublishPreview, enablePublishPreview,
activeChannelClaim, activeChannelClaim,
@ -124,9 +117,6 @@ function PublishForm(props: Props) {
isPostClaim, isPostClaim,
permanentUrl, permanentUrl,
remoteUrl, remoteUrl,
isClaimingInitialRewards,
claimInitialRewards,
hasClaimedInitialRewards,
} = props; } = props;
const inEditMode = Boolean(editingURI); const inEditMode = Boolean(editingURI);
@ -224,12 +214,6 @@ function PublishForm(props: Props) {
const [previewing, setPreviewing] = React.useState(false); const [previewing, setPreviewing] = React.useState(false);
useEffect(() => {
if (!hasClaimedInitialRewards) {
claimInitialRewards();
}
}, [hasClaimedInitialRewards, claimInitialRewards]);
useEffect(() => { useEffect(() => {
if (!modal) { if (!modal) {
setTimeout(() => { setTimeout(() => {
@ -240,9 +224,7 @@ function PublishForm(props: Props) {
let submitLabel; let submitLabel;
if (isClaimingInitialRewards) { if (publishing) {
submitLabel = __('Claiming credits...');
} else if (publishing) {
if (isStillEditing) { if (isStillEditing) {
submitLabel = __('Saving...'); submitLabel = __('Saving...');
} else { } else {
@ -536,12 +518,7 @@ function PublishForm(props: Props) {
onClick={handlePublish} onClick={handlePublish}
label={submitLabel} label={submitLabel}
disabled={ disabled={
isClaimingInitialRewards || formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS || previewing
formDisabled ||
!formValid ||
uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS ||
ytSignupPending ||
previewing
} }
/> />
<Button button="link" onClick={clearPublish} label={__('New --[clears Publish Form]--')} /> <Button button="link" onClick={clearPublish} label={__('New --[clears Publish Form]--')} />

View file

@ -6,7 +6,6 @@ import Card from 'component/common/card';
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize'; import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
import Button from 'component/button'; import Button from 'component/button';
import classnames from 'classnames'; import classnames from 'classnames';
import RecSys from 'recsys';
const VIEW_ALL_RELATED = 'view_all_related'; const VIEW_ALL_RELATED = 'view_all_related';
const VIEW_MORE_FROM = 'view_more_from'; const VIEW_MORE_FROM = 'view_more_from';
@ -18,48 +17,20 @@ type Props = {
isSearching: boolean, isSearching: boolean,
doFetchRecommendedContent: (string) => void, doFetchRecommendedContent: (string) => void,
claim: ?StreamClaim, claim: ?StreamClaim,
claimId: string,
}; };
export default React.memo<Props>(function RecommendedContent(props: Props) { export default React.memo<Props>(function RecommendedContent(props: Props) {
const { const { uri, doFetchRecommendedContent, recommendedContentUris, isSearching, claim } = props;
uri,
doFetchRecommendedContent,
recommendedContentUris,
nextRecommendedUri,
isSearching,
claim,
claimId,
} = props;
const [viewMode, setViewMode] = React.useState(VIEW_ALL_RELATED); const [viewMode, setViewMode] = React.useState(VIEW_ALL_RELATED);
const signingChannel = claim && claim.signing_channel; const signingChannel = claim && claim.signing_channel;
const channelName = signingChannel ? signingChannel.name : null; const channelName = signingChannel ? signingChannel.name : null;
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const isMedium = useIsMediumScreen(); const isMedium = useIsMediumScreen();
const { onRecsLoaded: onRecommendationsLoaded, onClickedRecommended: onRecommendationClicked } = RecSys;
React.useEffect(() => { React.useEffect(() => {
doFetchRecommendedContent(uri); doFetchRecommendedContent(uri);
}, [uri, doFetchRecommendedContent]); }, [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 ( return (
<Card <Card
isBodyList isBodyList
@ -96,7 +67,6 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
uris={recommendedContentUris} uris={recommendedContentUris}
hideMenu={isMobile} hideMenu={isMobile}
empty={__('No related content found')} empty={__('No related content found')}
onClick={handleRecommendationClicked}
/> />
)} )}
{viewMode === VIEW_MORE_FROM && signingChannel && ( {viewMode === VIEW_MORE_FROM && signingChannel && (

View file

@ -1,13 +1,10 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectHasNavigated, selectScrollStartingPosition, selectWelcomeVersion } from 'redux/selectors/app'; import { selectHasNavigated, selectScrollStartingPosition, selectWelcomeVersion } from 'redux/selectors/app';
import { selectHomepageData } from 'redux/selectors/settings'; import { selectHomepageData } from 'redux/selectors/settings';
import Router from './view'; import Router from './view';
import { normalizeURI } from 'util/lbryURI'; import { normalizeURI } from 'util/lbryURI';
import { selectTitleForUri } from 'redux/selectors/claims'; import { selectTitleForUri } from 'redux/selectors/claims';
import { doSetHasNavigated } from 'redux/actions/app'; import { doSetHasNavigated } from 'redux/actions/app';
import { doUserSetReferrer } from 'redux/actions/user';
import { selectHasUnclaimedRefereeReward } from 'redux/selectors/rewards';
const select = (state) => { const select = (state) => {
const { pathname, hash } = state.router.location; const { pathname, hash } = state.router.location;
@ -30,17 +27,14 @@ const select = (state) => {
uri, uri,
title: selectTitleForUri(state, uri), title: selectTitleForUri(state, uri),
currentScroll: selectScrollStartingPosition(state), currentScroll: selectScrollStartingPosition(state),
isAuthenticated: selectUserVerifiedEmail(state),
welcomeVersion: selectWelcomeVersion(state), welcomeVersion: selectWelcomeVersion(state),
hasNavigated: selectHasNavigated(state), hasNavigated: selectHasNavigated(state),
hasUnclaimedRefereeReward: selectHasUnclaimedRefereeReward(state),
homepageData: selectHomepageData(state), homepageData: selectHomepageData(state),
}; };
}; };
const perform = (dispatch) => ({ const perform = (dispatch) => ({
setHasNavigated: () => dispatch(doSetHasNavigated()), setHasNavigated: () => dispatch(doSetHasNavigated()),
setReferrer: (referrer) => dispatch(doUserSetReferrer(referrer)),
}); });
export default connect(select, perform)(Router); export default connect(select, perform)(Router);

View file

@ -5,7 +5,7 @@ import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import { PAGE_TITLE } from 'constants/pageTitles'; import { PAGE_TITLE } from 'constants/pageTitles';
import { LINKED_COMMENT_QUERY_PARAM } from 'constants/comment'; 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 { WELCOME_VERSION } from 'config';
import { GetLinksData } from 'util/buildHomepage'; import { GetLinksData } from 'util/buildHomepage';
import { useIsLargeScreen } from 'effects/use-screensize'; import { useIsLargeScreen } from 'effects/use-screensize';
@ -14,14 +14,9 @@ import HomePage from 'page/home';
import BackupPage from 'page/backup'; import BackupPage from 'page/backup';
// Chunk: "secondary" // Chunk: "secondary"
import SignInPage from 'page/signIn';
import SignInWalletPasswordPage from 'page/signInWalletPassword'; import SignInWalletPasswordPage from 'page/signInWalletPassword';
import SignUpPage from 'page/signUp';
import SignInVerifyPage from 'page/signInVerify';
// Chunk: "wallet/secondary" // Chunk: "wallet/secondary"
import BuyPage from 'page/buy';
import ReceivePage from 'page/receive'; import ReceivePage from 'page/receive';
import SendPage from 'page/send'; import SendPage from 'page/send';
import WalletPage from 'page/wallet'; import WalletPage from 'page/wallet';
@ -43,8 +38,6 @@ import ListBlockedPage from 'page/listBlocked';
import ListsPage from 'page/lists'; import ListsPage from 'page/lists';
import PlaylistsPage from 'page/playlists'; import PlaylistsPage from 'page/playlists';
import OwnComments from 'page/ownComments'; import OwnComments from 'page/ownComments';
import PasswordResetPage from 'page/passwordReset';
import PasswordSetPage from 'page/passwordSet';
import PublishPage from 'page/publish'; import PublishPage from 'page/publish';
import ReportContentPage from 'page/reportContent'; import ReportContentPage from 'page/reportContent';
import ReportPage from 'page/report'; import ReportPage from 'page/report';
@ -53,6 +46,7 @@ import SearchPage from 'page/search';
import SettingsCreatorPage from 'page/settingsCreator'; import SettingsCreatorPage from 'page/settingsCreator';
import SettingsNotificationsPage from 'page/settingsNotifications'; import SettingsNotificationsPage from 'page/settingsNotifications';
import SettingsSyncPage from 'page/settingsSync';
import SettingsPage from 'page/settings'; import SettingsPage from 'page/settings';
import ShowPage from 'page/show'; import ShowPage from 'page/show';
@ -70,7 +64,6 @@ if ('scrollRestoration' in history) {
type Props = { type Props = {
currentScroll: number, currentScroll: number,
isAuthenticated: boolean,
location: { pathname: string, search: string, hash: string }, location: { pathname: string, search: string, hash: string },
history: { history: {
action: string, action: string,
@ -90,14 +83,12 @@ type Props = {
welcomeVersion: number, welcomeVersion: number,
hasNavigated: boolean, hasNavigated: boolean,
setHasNavigated: () => void, setHasNavigated: () => void,
setReferrer: (?string) => void,
hasUnclaimedRefereeReward: boolean,
homepageData: any, homepageData: any,
}; };
type PrivateRouteProps = Props & { type PrivateRouteProps = Props & {
component: any, component: any,
isAuthenticated: boolean, isAuthenticated?: boolean,
}; };
function PrivateRoute(props: PrivateRouteProps) { function PrivateRoute(props: PrivateRouteProps) {
@ -109,15 +100,12 @@ function AppRouter(props: Props) {
const { const {
currentScroll, currentScroll,
location: { pathname, search, hash }, location: { pathname, search, hash },
isAuthenticated,
history, history,
uri, uri,
title, title,
welcomeVersion, welcomeVersion,
hasNavigated, hasNavigated,
setHasNavigated, setHasNavigated,
hasUnclaimedRefereeReward,
setReferrer,
homepageData, homepageData,
} = props; } = props;
const { entries, listen, action: historyAction } = history; const { entries, listen, action: historyAction } = history;
@ -140,16 +128,6 @@ function AppRouter(props: Props) {
return unlisten; return unlisten;
}, [listen, hasNavigated, setHasNavigated]); }, [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(() => { useEffect(() => {
const getDefaultTitle = (pathname: string) => { const getDefaultTitle = (pathname: string) => {
const title = pathname.startsWith('/$/') ? PAGE_TITLE[pathname.substring(3)] : ''; const title = pathname.startsWith('/$/') ? PAGE_TITLE[pathname.substring(3)] : '';
@ -175,9 +153,7 @@ function AppRouter(props: Props) {
document.title = getDefaultTitle(pathname); document.title = getDefaultTitle(pathname);
} }
// @if TARGET='app'
entries[entryIndex].title = document.title; entries[entryIndex].title = document.title;
// @endif
}, [pathname, entries, entryIndex, title, uri]); }, [pathname, entries, entryIndex, title, uri]);
useEffect(() => { useEffect(() => {
@ -227,19 +203,10 @@ function AppRouter(props: Props) {
))} ))}
{/* Odysee signin */} {/* 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.WELCOME}`} exact component={Welcome} />
<Route path={`/$/${PAGES.SETTINGS_SYNC}`} exact component={SettingsSyncPage} />
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} /> <Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
{/* @if TARGET='app' */}
<Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} /> <Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} />
{/* @endif */}
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} /> <Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
<Route path={`/$/${PAGES.TOP}`} exact component={TopPage} /> <Route path={`/$/${PAGES.TOP}`} exact component={TopPage} />
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} /> <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.SETTINGS_CREATOR}`} component={SettingsCreatorPage} />
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} /> <PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} /> <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.RECEIVE}`} component={ReceivePage} />
<PrivateRoute {...props} path={`/$/${PAGES.SEND}`} component={SendPage} /> <PrivateRoute {...props} path={`/$/${PAGES.SEND}`} component={SendPage} />
<PrivateRoute {...props} path={`/$/${PAGES.NOTIFICATIONS}`} component={NotificationsPage} /> <PrivateRoute {...props} path={`/$/${PAGES.NOTIFICATIONS}`} component={NotificationsPage} />

View file

@ -3,7 +3,6 @@ import SelectChannel from './view';
import { selectBalance } from 'redux/selectors/wallet'; import { selectBalance } from 'redux/selectors/wallet';
import { selectMyChannelClaims, selectFetchingMyChannels } from 'redux/selectors/claims'; import { selectMyChannelClaims, selectFetchingMyChannels } from 'redux/selectors/claims';
import { doFetchChannelListMine, doCreateChannel } from 'redux/actions/claims'; import { doFetchChannelListMine, doCreateChannel } from 'redux/actions/claims';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectActiveChannelClaim } from 'redux/selectors/app'; import { selectActiveChannelClaim } from 'redux/selectors/app';
import { doSetActiveChannel } from 'redux/actions/app'; import { doSetActiveChannel } from 'redux/actions/app';
@ -11,7 +10,6 @@ const select = (state) => ({
myChannelClaims: selectMyChannelClaims(state), myChannelClaims: selectMyChannelClaims(state),
fetchingChannels: selectFetchingMyChannels(state), fetchingChannels: selectFetchingMyChannels(state),
balance: selectBalance(state), balance: selectBalance(state),
emailVerified: selectUserVerifiedEmail(state),
activeChannelClaim: selectActiveChannelClaim(state), activeChannelClaim: selectActiveChannelClaim(state),
}); });

View file

@ -2,14 +2,11 @@ import { connect } from 'react-redux';
import { selectHasChannels } from 'redux/selectors/claims'; import { selectHasChannels } from 'redux/selectors/claims';
import { selectWalletIsEncrypted } from 'redux/selectors/wallet'; import { selectWalletIsEncrypted } from 'redux/selectors/wallet';
import { doWalletStatus } from 'redux/actions/wallet'; import { doWalletStatus } from 'redux/actions/wallet';
import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
import SettingAccount from './view'; import SettingAccount from './view';
const select = (state) => ({ const select = (state) => ({
isAuthenticated: selectUserVerifiedEmail(state),
walletEncrypted: selectWalletIsEncrypted(state), walletEncrypted: selectWalletIsEncrypted(state),
user: selectUser(state),
hasChannels: selectHasChannels(state), hasChannels: selectHasChannels(state),
}); });

View file

@ -6,27 +6,26 @@ import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import Card from 'component/common/card'; import Card from 'component/common/card';
import SettingsRow from 'component/settingsRow'; 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'; import { getPasswordFromCookie } from 'util/saved-passwords';
type Props = { type Props = {
isAuthenticated: boolean,
walletEncrypted: boolean, walletEncrypted: boolean,
user: User,
hasChannels: boolean, hasChannels: boolean,
doWalletStatus: () => void, doWalletStatus: () => void,
}; };
export default function SettingAccount(props: Props) { export default function SettingAccount(props: Props) {
const { isAuthenticated, walletEncrypted, hasChannels, doWalletStatus } = props; const { hasChannels, doWalletStatus } = props;
const [storedPassword, setStoredPassword] = React.useState(false); // const [storedPassword, setStoredPassword] = React.useState(false);
// Determine if password is stored. // Determine if password is stored.
React.useEffect(() => { React.useEffect(() => {
doWalletStatus(); doWalletStatus();
getPasswordFromCookie().then((p) => { getPasswordFromCookie().then((p) => {
if (typeof p === 'string') { if (typeof p === 'string') {
setStoredPassword(true); // get password
} }
}); });
}, []); // eslint-disable-line react-hooks/exhaustive-deps }, []); // eslint-disable-line react-hooks/exhaustive-deps
@ -42,18 +41,8 @@ export default function SettingAccount(props: Props) {
isBodyList isBodyList
body={ body={
<> <>
{isAuthenticated && ( {/* This will probably start the new sync flow when checked (-> openModal(SYNC_ENABLE) ) */}
<SettingsRow title={__('Password')}> {/* <SyncToggle disabled={true} /> */}
<Button
button="inverse"
label={__('Manage')}
icon={ICONS.ARROW_RIGHT}
navigate={`/$/${PAGES.SETTINGS_UPDATE_PWD}`}
/>
</SettingsRow>
)}
<SyncToggle disabled={walletEncrypted && !storedPassword && storedPassword !== ''} />
{hasChannels && ( {hasChannels && (
<SettingsRow title={__('Comments')} subtitle={__('View your past comments.')}> <SettingsRow title={__('Comments')} subtitle={__('View your past comments.')}>

View file

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

View file

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

View file

@ -4,12 +4,10 @@ import * as SETTINGS from 'constants/settings';
import { doSetPlayingUri, clearContentCache } from 'redux/actions/content'; import { doSetPlayingUri, clearContentCache } from 'redux/actions/content';
import { doSetClientSetting } from 'redux/actions/settings'; import { doSetClientSetting } from 'redux/actions/settings';
import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings'; import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import SettingContent from './view'; import SettingContent from './view';
const select = (state) => ({ const select = (state) => ({
isAuthenticated: selectUserVerifiedEmail(state),
floatingPlayer: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state), floatingPlayer: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state),
autoplayMedia: makeSelectClientSetting(SETTINGS.AUTOPLAY_MEDIA)(state), autoplayMedia: makeSelectClientSetting(SETTINGS.AUTOPLAY_MEDIA)(state),
autoplayNext: makeSelectClientSetting(SETTINGS.AUTOPLAY_NEXT)(state), autoplayNext: makeSelectClientSetting(SETTINGS.AUTOPLAY_NEXT)(state),

View file

@ -3,7 +3,6 @@ import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import React from 'react'; import React from 'react';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { Lbryio } from 'lbryinc';
import { SETTINGS_GRP } from 'constants/settings'; import { SETTINGS_GRP } from 'constants/settings';
import Button from 'component/button'; import Button from 'component/button';
import Card from 'component/common/card'; import Card from 'component/common/card';
@ -18,7 +17,6 @@ type Price = {
type Props = { type Props = {
// --- select --- // --- select ---
isAuthenticated: boolean,
floatingPlayer: boolean, floatingPlayer: boolean,
autoplayMedia: boolean, autoplayMedia: boolean,
autoplayNext: boolean, autoplayNext: boolean,
@ -37,7 +35,6 @@ type Props = {
export default function SettingContent(props: Props) { export default function SettingContent(props: Props) {
const { const {
isAuthenticated,
floatingPlayer, floatingPlayer,
autoplayMedia, autoplayMedia,
autoplayNext, autoplayNext,
@ -110,10 +107,6 @@ export default function SettingContent(props: Props) {
type="checkbox" type="checkbox"
name="hide_reposts" name="hide_reposts"
onChange={(e) => { onChange={(e) => {
if (isAuthenticated) {
let param = e.target.checked ? { add: 'noreposts' } : { remove: 'noreposts' };
Lbryio.call('user_tag', 'edit', param);
}
setClientSetting(SETTINGS.HIDE_REPOSTS, !hideReposts); setClientSetting(SETTINGS.HIDE_REPOSTS, !hideReposts);
}} }}
checked={hideReposts} checked={hideReposts}
@ -172,7 +165,7 @@ export default function SettingContent(props: Props) {
</SettingsRow> </SettingsRow>
{myChannelUrls && myChannelUrls.length > 0 && ( {myChannelUrls && myChannelUrls.length > 0 && (
<SettingsRow title={__('Creator settings')}> <SettingsRow title={__('Creator Comment settings')}>
<Button <Button
button="inverse" button="inverse"
label={__('Manage')} label={__('Manage')}

View file

@ -6,12 +6,12 @@ import {
doNotifyDecryptWallet, doNotifyDecryptWallet,
doNotifyEncryptWallet, doNotifyEncryptWallet,
doNotifyForgetPassword, doNotifyForgetPassword,
doOpenModal,
doToggle3PAnalytics, doToggle3PAnalytics,
} from 'redux/actions/app'; } from 'redux/actions/app';
import { doSetDaemonSetting, doClearDaemonSetting, doFindFFmpeg } from 'redux/actions/settings'; import { doSetDaemonSetting, doClearDaemonSetting, doFindFFmpeg } from 'redux/actions/settings';
import { selectAllowAnalytics } from 'redux/selectors/app'; import { selectAllowAnalytics } from 'redux/selectors/app';
import { selectDaemonSettings, selectFfmpegStatus, selectFindingFFmpeg } from 'redux/selectors/settings'; import { selectDaemonSettings, selectFfmpegStatus, selectFindingFFmpeg } from 'redux/selectors/settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user'; // here
import SettingSystem from './view'; import SettingSystem from './view';
@ -20,7 +20,6 @@ const select = (state) => ({
ffmpegStatus: selectFfmpegStatus(state), ffmpegStatus: selectFfmpegStatus(state),
findingFFmpeg: selectFindingFFmpeg(state), findingFFmpeg: selectFindingFFmpeg(state),
walletEncrypted: selectWalletIsEncrypted(state), walletEncrypted: selectWalletIsEncrypted(state),
isAuthenticated: selectUserVerifiedEmail(state),
allowAnalytics: selectAllowAnalytics(state), allowAnalytics: selectAllowAnalytics(state),
}); });
@ -34,6 +33,7 @@ const perform = (dispatch) => ({
updateWalletStatus: () => dispatch(doWalletStatus()), updateWalletStatus: () => dispatch(doWalletStatus()),
confirmForgetPassword: (modalProps) => dispatch(doNotifyForgetPassword(modalProps)), confirmForgetPassword: (modalProps) => dispatch(doNotifyForgetPassword(modalProps)),
toggle3PAnalytics: (allow) => dispatch(doToggle3PAnalytics(allow)), toggle3PAnalytics: (allow) => dispatch(doToggle3PAnalytics(allow)),
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
}); });
export default connect(select, perform)(SettingSystem); export default connect(select, perform)(SettingSystem);

View file

@ -18,6 +18,8 @@ import { getPasswordFromCookie } from 'util/saved-passwords';
import * as DAEMON_SETTINGS from 'constants/daemon_settings'; import * as DAEMON_SETTINGS from 'constants/daemon_settings';
import SettingEnablePrereleases from 'component/settingEnablePrereleases'; import SettingEnablePrereleases from 'component/settingEnablePrereleases';
import SettingDisableAutoUpdates from 'component/settingDisableAutoUpdates'; import SettingDisableAutoUpdates from 'component/settingDisableAutoUpdates';
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
const IS_MAC = process.platform === 'darwin'; const IS_MAC = process.platform === 'darwin';
@ -44,7 +46,6 @@ type Props = {
ffmpegStatus: { available: boolean, which: string }, ffmpegStatus: { available: boolean, which: string },
findingFFmpeg: boolean, findingFFmpeg: boolean,
walletEncrypted: boolean, walletEncrypted: boolean,
isAuthenticated: boolean,
allowAnalytics: boolean, allowAnalytics: boolean,
// --- perform --- // --- perform ---
setDaemonSetting: (string, ?SetDaemonSettingArg) => void, setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
@ -64,7 +65,6 @@ export default function SettingSystem(props: Props) {
ffmpegStatus, ffmpegStatus,
findingFFmpeg, findingFFmpeg,
walletEncrypted, walletEncrypted,
isAuthenticated,
allowAnalytics, allowAnalytics,
setDaemonSetting, setDaemonSetting,
clearDaemonSetting, clearDaemonSetting,
@ -150,6 +150,14 @@ export default function SettingSystem(props: Props) {
checked={daemonSettings.save_files} checked={daemonSettings.save_files}
/> />
</SettingsRow> </SettingsRow>
<SettingsRow title={__('Remote Sync Settings')}>
<Button
button="inverse"
label={__('Manage')}
icon={ICONS.ARROW_RIGHT}
navigate={`/$/${PAGES.SETTINGS_SYNC}`}
/>
</SettingsRow>
<SettingsRow <SettingsRow
title={__('Share usage and diagnostic data')} title={__('Share usage and diagnostic data')}
subtitle={ subtitle={
@ -168,12 +176,7 @@ export default function SettingSystem(props: Props) {
onChange={() => setDaemonSetting('share_usage_data', !daemonSettings.share_usage_data)} onChange={() => setDaemonSetting('share_usage_data', !daemonSettings.share_usage_data)}
checked={daemonSettings.share_usage_data} checked={daemonSettings.share_usage_data}
label={<React.Fragment>{__('Allow the app to share data to LBRY.inc')}</React.Fragment>} label={<React.Fragment>{__('Allow the app to share data to LBRY.inc')}</React.Fragment>}
helper={ helper={__('Internal sharing is required to participate in rewards programs.')}
isAuthenticated
? __('Internal sharing is required while signed in.')
: __('Internal sharing is required to participate in rewards programs.')
}
disabled={isAuthenticated && daemonSettings.share_usage_data}
/> />
<FormField <FormField
type="checkbox" type="checkbox"
@ -276,24 +279,13 @@ export default function SettingSystem(props: Props) {
title={__('Encrypt my wallet with a custom password')} title={__('Encrypt my wallet with a custom password')}
subtitle={ subtitle={
<React.Fragment> <React.Fragment>
<I18nMessage {__('Secure your local wallet data with a custom password.')}{' '}
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> <strong>{__('Lost passwords cannot be recovered.')} </strong>
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />. */} <Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />
</React.Fragment> </React.Fragment>
} }
> >
<FormField <FormField
disabled
type="checkbox" type="checkbox"
name="encrypt_wallet" name="encrypt_wallet"
onChange={() => onChangeEncryptWallet()} onChange={() => onChangeEncryptWallet()}

View file

@ -2,7 +2,6 @@ import { connect } from 'react-redux';
import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { doClearPurchasedUriSuccess } from 'redux/actions/file'; import { doClearPurchasedUriSuccess } from 'redux/actions/file';
import { selectFollowedTags } from 'redux/selectors/tags'; import { selectFollowedTags } from 'redux/selectors/tags';
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
import { selectHomepageData } from 'redux/selectors/settings'; import { selectHomepageData } from 'redux/selectors/settings';
import { doSignOut } from 'redux/actions/app'; import { doSignOut } from 'redux/actions/app';
import { selectUnseenNotificationCount } from 'redux/selectors/notifications'; import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
@ -13,10 +12,8 @@ import SideNavigation from './view';
const select = (state) => ({ const select = (state) => ({
subscriptions: selectSubscriptions(state), subscriptions: selectSubscriptions(state),
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
email: selectUserVerifiedEmail(state),
purchaseSuccess: selectPurchaseUriSuccess(state), purchaseSuccess: selectPurchaseUriSuccess(state),
unseenCount: selectUnseenNotificationCount(state), unseenCount: selectUnseenNotificationCount(state),
user: selectUser(state),
homepageData: selectHomepageData(state), homepageData: selectHomepageData(state),
}); });

View file

@ -6,13 +6,11 @@ import * as KEYCODES from 'constants/keycodes';
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import classnames from 'classnames'; import classnames from 'classnames';
import NotificationBubble from 'component/notificationBubble';
import DebouncedInput from 'component/common/debounced-input'; import DebouncedInput from 'component/common/debounced-input';
import ChannelThumbnail from 'component/channelThumbnail'; import ChannelThumbnail from 'component/channelThumbnail';
import { useIsMobile, isTouch } from 'effects/use-screensize'; import { useIsMobile, isTouch } from 'effects/use-screensize';
import { IS_MAC } from 'component/app/view'; import { IS_MAC } from 'component/app/view';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { ENABLE_UI_NOTIFICATIONS } from 'config';
const FOLLOWED_ITEM_INITIAL_LIMIT = 10; const FOLLOWED_ITEM_INITIAL_LIMIT = 10;
const touch = isTouch(); const touch = isTouch();
@ -32,7 +30,6 @@ type SideNavLink = {
type Props = { type Props = {
subscriptions: Array<Subscription>, subscriptions: Array<Subscription>,
followedTags: Array<Tag>, followedTags: Array<Tag>,
email: ?string,
uploadCount: number, uploadCount: number,
doSignOut: () => void, doSignOut: () => void,
sidebarOpen: boolean, sidebarOpen: boolean,
@ -42,7 +39,6 @@ type Props = {
unseenCount: number, unseenCount: number,
purchaseSuccess: boolean, purchaseSuccess: boolean,
doClearPurchasedUriSuccess: () => void, doClearPurchasedUriSuccess: () => void,
user: ?User,
homepageData: any, homepageData: any,
activeChannelStakedLevel: number, activeChannelStakedLevel: number,
}; };
@ -51,7 +47,6 @@ function SideNavigation(props: Props) {
const { const {
subscriptions, subscriptions,
doSignOut, doSignOut,
email,
purchaseSuccess, purchaseSuccess,
doClearPurchasedUriSuccess, doClearPurchasedUriSuccess,
sidebarOpen, sidebarOpen,
@ -59,7 +54,6 @@ function SideNavigation(props: Props) {
isMediumScreen, isMediumScreen,
isOnFilePage, isOnFilePage,
unseenCount, unseenCount,
user,
followedTags, followedTags,
} = props; } = props;
@ -99,13 +93,6 @@ function SideNavigation(props: Props) {
icon: ICONS.PURCHASED, icon: ICONS.PURCHASED,
}; };
const NOTIFICATIONS = {
title: 'Notifications',
link: `/$/${PAGES.NOTIFICATIONS}`,
icon: ICONS.NOTIFICATION,
extra: <NotificationBubble inline />,
};
const PLAYLISTS = { const PLAYLISTS = {
title: 'Lists', title: 'Lists',
link: `/$/${PAGES.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 [pulseLibrary, setPulseLibrary] = React.useState(false);
const [expandSubscriptions, setExpandSubscriptions] = React.useState(false); const [expandSubscriptions, setExpandSubscriptions] = React.useState(false);
const [expandTags, setExpandTags] = React.useState(false); const [expandTags, setExpandTags] = React.useState(false);
@ -241,10 +226,6 @@ function SideNavigation(props: Props) {
const { hideForUnauth, route, link, ...passedProps } = props; const { hideForUnauth, route, link, ...passedProps } = props;
const { title, icon, extra } = passedProps; const { title, icon, extra } = passedProps;
if (hideForUnauth && !email) {
return null;
}
return ( return (
<li key={route || link || title}> <li key={route || link || title}>
<Button <Button
@ -388,15 +369,11 @@ function SideNavigation(props: Props) {
'navigation--push': showPushMenu, 'navigation--push': showPushMenu,
'navigation-file-page-and-mobile': hideMenuFromView, 'navigation-file-page-and-mobile': hideMenuFromView,
'navigation-touch': touch, 'navigation-touch': touch,
// @if TARGET='app'
'navigation--mac': IS_MAC, 'navigation--mac': IS_MAC,
// @endif
})} })}
> >
{(!canDisposeMenu || sidebarOpen) && ( {(!canDisposeMenu || sidebarOpen) && (
<div className="navigation-inner-container"> <div className="navigation-inner-container">
<ul className="navigation-links--absolute mobile-only">{notificationsEnabled && getLink(NOTIFICATIONS)}</ul>
<ul <ul
className={classnames('navigation-links', { className={classnames('navigation-links', {
'navigation-links--micro': showMicroMenu, 'navigation-links--micro': showMicroMenu,
@ -412,7 +389,7 @@ function SideNavigation(props: Props) {
{getLink(PLAYLISTS)} {getLink(PLAYLISTS)}
</ul> </ul>
<ul className="navigation-links--absolute mobile-only"> <ul className="navigation-links--absolute mobile-only">
{email && MOBILE_LINKS.map((linkProps) => getLink(linkProps))} {MOBILE_LINKS.map((linkProps) => getLink(linkProps))}
</ul> </ul>
{getSubscriptionSection()} {getSubscriptionSection()}

View file

@ -63,8 +63,6 @@ function SocialShare(props: Props) {
const shareUrl: string = generateShareUrl( const shareUrl: string = generateShareUrl(
shareDomain, shareDomain,
lbryUrl, lbryUrl,
null,
null,
includeStartTime, includeStartTime,
startTimeSeconds, startTimeSeconds,
includedCollectionId includedCollectionId

View file

@ -6,7 +6,6 @@ import {
makeSelectNotificationsDisabled, makeSelectNotificationsDisabled,
} from 'redux/selectors/subscriptions'; } from 'redux/selectors/subscriptions';
import { makeSelectPermanentUrlForUri } from 'redux/selectors/claims'; import { makeSelectPermanentUrlForUri } from 'redux/selectors/claims';
import { selectUser } from 'redux/selectors/user';
import { doToast } from 'redux/actions/notifications'; import { doToast } from 'redux/actions/notifications';
import SubscribeButton from './view'; import SubscribeButton from './view';
@ -15,7 +14,6 @@ const select = (state, props) => ({
firstRunCompleted: selectFirstRunCompleted(state), firstRunCompleted: selectFirstRunCompleted(state),
permanentUrl: makeSelectPermanentUrlForUri(props.uri)(state), permanentUrl: makeSelectPermanentUrlForUri(props.uri)(state),
notificationsDisabled: makeSelectNotificationsDisabled(props.uri)(state), notificationsDisabled: makeSelectNotificationsDisabled(props.uri)(state),
user: selectUser(state),
}); });
export default connect(select, { export default connect(select, {

View file

@ -21,7 +21,6 @@ type Props = {
doToast: ({ message: string }) => void, doToast: ({ message: string }) => void,
shrinkOnMobile: boolean, shrinkOnMobile: boolean,
notificationsDisabled: boolean, notificationsDisabled: boolean,
user: ?User,
uri: string, uri: string,
}; };
@ -34,7 +33,6 @@ export default function SubscribeButton(props: Props) {
doToast, doToast,
shrinkOnMobile = false, shrinkOnMobile = false,
notificationsDisabled, notificationsDisabled,
user,
uri, uri,
} = props; } = props;
@ -42,7 +40,7 @@ export default function SubscribeButton(props: Props) {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
let isHovering = useHover(buttonRef); let isHovering = useHover(buttonRef);
isHovering = isMobile ? true : isHovering; isHovering = isMobile ? true : isHovering;
const uiNotificationsEnabled = (user && user.experimental_ui) || ENABLE_UI_NOTIFICATIONS; const uiNotificationsEnabled = ENABLE_UI_NOTIFICATIONS;
const { channelName: rawChannelName } = parseURI(uri); const { channelName: rawChannelName } = parseURI(uri);

View file

@ -1,6 +1,5 @@
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { import {
selectGetSyncErrorMessage, selectGetSyncErrorMessage,
selectHasSyncedWallet, selectHasSyncedWallet,
@ -17,7 +16,6 @@ const select = (state) => ({
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state), syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
hasSyncedWallet: selectHasSyncedWallet(state), hasSyncedWallet: selectHasSyncedWallet(state),
hasSyncChanged: selectHashChanged(state), hasSyncChanged: selectHashChanged(state),
verifiedEmail: selectUserVerifiedEmail(state),
getSyncError: selectGetSyncErrorMessage(state), getSyncError: selectGetSyncErrorMessage(state),
getSyncPending: selectGetSyncIsPending(state), getSyncPending: selectGetSyncIsPending(state),
}); });

View file

@ -1,18 +1,17 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectGetSyncIsPending, selectSyncApplyPasswordError } from 'redux/selectors/sync'; import { selectGetSyncIsPending, selectSyncApplyPasswordError } from 'redux/selectors/sync';
import { doGetSyncDesktop } from 'redux/actions/sync'; import { doGetSyncDesktop } from 'redux/actions/sync';
import { selectUserEmail } from 'redux/selectors/user';
import { doSetClientSetting } from 'redux/actions/settings'; import { doSetClientSetting } from 'redux/actions/settings';
import { doSignOut, doHandleSyncComplete } from 'redux/actions/app'; import { doSignOut, doHandleSyncComplete } from 'redux/actions/app';
import SyncPassword from './view'; import SyncPassword from './view';
const select = state => ({ const select = (state) => ({
getSyncIsPending: selectGetSyncIsPending(state), getSyncIsPending: selectGetSyncIsPending(state),
email: selectUserEmail(state),
passwordError: selectSyncApplyPasswordError(state), passwordError: selectSyncApplyPasswordError(state),
// bring email in from new sync system
}); });
const perform = dispatch => ({ const perform = (dispatch) => ({
getSync: (cb, password) => dispatch(doGetSyncDesktop(cb, password)), getSync: (cb, password) => dispatch(doGetSyncDesktop(cb, password)),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)), setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
handleSyncComplete: (error, hasDataChanged) => dispatch(doHandleSyncComplete(error, hasDataChanged)), handleSyncComplete: (error, hasDataChanged) => dispatch(doHandleSyncComplete(error, hasDataChanged)),

View file

@ -12,14 +12,14 @@ import { SITE_HELP_EMAIL } from 'config';
type Props = { type Props = {
getSync: ((any, boolean) => void, ?string) => void, getSync: ((any, boolean) => void, ?string) => void,
getSyncIsPending: boolean, getSyncIsPending: boolean,
email: string,
passwordError: boolean, passwordError: boolean,
signOut: () => void, signOut: () => void,
handleSyncComplete: (any, boolean) => void, handleSyncComplete: (any, boolean) => void,
email: string,
}; };
function SyncPassword(props: Props) { function SyncPassword(props: Props) {
const { getSync, getSyncIsPending, email, signOut, passwordError, handleSyncComplete } = props; const { getSync, getSyncIsPending, signOut, passwordError, handleSyncComplete, email = 'dummy' } = props;
const { const {
push, push,
location: { search }, location: { search },

View file

@ -1,6 +1,5 @@
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectGetSyncErrorMessage } from 'redux/selectors/sync'; import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetWalletSyncPreference } from 'redux/actions/settings'; import { doSetWalletSyncPreference } from 'redux/actions/settings';
@ -9,7 +8,6 @@ import SyncToggle from './view';
const select = (state) => ({ const select = (state) => ({
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state), syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
verifiedEmail: selectUserVerifiedEmail(state),
getSyncError: selectGetSyncErrorMessage(state), getSyncError: selectGetSyncErrorMessage(state),
}); });

View file

@ -1,7 +1,6 @@
// @flow // @flow
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import React from 'react'; import React from 'react';
import Button from 'component/button';
import SettingsRow from 'component/settingsRow'; import SettingsRow from 'component/settingsRow';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
@ -9,7 +8,6 @@ import { FormField } from 'component/common/form';
type Props = { type Props = {
setSyncEnabled: (boolean) => void, setSyncEnabled: (boolean) => void,
syncEnabled: boolean, syncEnabled: boolean,
verifiedEmail: ?string,
history: { push: (string) => void }, history: { push: (string) => void },
location: UrlLocation, location: UrlLocation,
getSyncError: ?string, getSyncError: ?string,
@ -18,32 +16,24 @@ type Props = {
}; };
function SyncToggle(props: Props) { function SyncToggle(props: Props) {
const { verifiedEmail, openModal, syncEnabled, disabled } = props; // Redesign for new sync system
const { openModal, syncEnabled, disabled } = props;
return ( return (
<SettingsRow <SettingsRow title={__('Sync')} subtitle={disabled ? '' : __('Sync your balance and preferences across devices.')}>
title={__('Sync')}
subtitle={disabled || !verifiedEmail ? '' : __('Sync your balance and preferences across devices.')}
>
<FormField <FormField
type="checkbox" type="checkbox"
name="sync_toggle" name="sync_toggle"
label={disabled || !verifiedEmail ? __('Sync your balance and preferences across devices.') : undefined} label={disabled ? __('Sync your balance and preferences across devices.') : undefined}
checked={syncEnabled && verifiedEmail} checked={syncEnabled}
onChange={() => openModal(MODALS.SYNC_ENABLE, { mode: syncEnabled ? 'disable' : 'enable' })} onChange={() => openModal(MODALS.SYNC_ENABLE, { mode: syncEnabled ? 'disable' : 'enable' })}
disabled={disabled || !verifiedEmail} disabled={disabled}
helper={ helper={
disabled disabled
? __("To enable Sync, close LBRY completely and check 'Remember Password' during wallet unlock.") ? __("To enable Sync, close LBRY completely and check 'Remember Password' during wallet unlock.")
: null : null
} }
/> />
{!verifiedEmail && (
<div>
<p className="help">{__('An email address is required to sync your account.')}</p>
<Button button="primary" label={__('Add Email')} />
</div>
)}
</SettingsRow> </SettingsRow>
); );
} }

View file

@ -1,13 +1,11 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUnfollowedTags, selectFollowedTags } from 'redux/selectors/tags'; import { selectUnfollowedTags, selectFollowedTags } from 'redux/selectors/tags';
import { doToggleTagFollowDesktop, doAddTag, doDeleteTag } from 'redux/actions/tags'; import { doToggleTagFollowDesktop, doAddTag, doDeleteTag } from 'redux/actions/tags';
import { selectUser } from 'redux/selectors/user';
import DiscoveryFirstRun from './view'; import DiscoveryFirstRun from './view';
const select = (state, props) => ({ const select = (state, props) => ({
unfollowedTags: props.unfollowedTags || selectUnfollowedTags(state), unfollowedTags: props.unfollowedTags || selectUnfollowedTags(state),
followedTags: props.followedTags || selectFollowedTags(state), followedTags: props.followedTags || selectFollowedTags(state),
user: selectUser(state),
}); });
export default connect(select, { export default connect(select, {

View file

@ -26,7 +26,6 @@ type Props = {
disabled?: boolean, disabled?: boolean,
limitSelect?: number, limitSelect?: number,
limitShow?: number, limitShow?: number,
user: User,
disableControlTags?: boolean, disableControlTags?: boolean,
}; };
@ -60,7 +59,6 @@ export default function TagsSearch(props: Props) {
disabled, disabled,
limitSelect = TAG_FOLLOW_MAX, limitSelect = TAG_FOLLOW_MAX,
limitShow = 5, limitShow = 5,
user,
disableControlTags, disableControlTags,
} = props; } = props;
const [newTag, setNewTag] = useState(''); const [newTag, setNewTag] = useState('');
@ -71,7 +69,7 @@ export default function TagsSearch(props: Props) {
// Make sure there are no duplicates, then trim // Make sure there are no duplicates, then trim
// suggestedTags = (followedTags - tagsPassedIn) + unfollowedTags // suggestedTags = (followedTags - tagsPassedIn) + unfollowedTags
const experimentalFeature = user && user.experimental_ui; const experimentalFeature = false;
const followedTagsSet = new Set(followedTags.map((tag) => tag.name)); const followedTagsSet = new Set(followedTags.map((tag) => tag.name));
const selectedTagsSet = new Set(tagsPassedIn.map((tag) => tag.name)); const selectedTagsSet = new Set(tagsPassedIn.map((tag) => tag.name));
const unfollowedTagsSet = new Set(unfollowedTags.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 maxed = Boolean(limitSelect && countWithoutSpecialTags >= limitSelect);
const suggestedTags = Array.from(suggestedTagsSet).filter(doesTagMatch).slice(0, limitShow); const suggestedTags = Array.from(suggestedTagsSet).filter(doesTagMatch).slice(0, limitShow);

View file

@ -1,11 +1,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectClaimedRewardsByTransactionId } from 'redux/selectors/rewards';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
import { selectIsFetchingTxos } from 'redux/selectors/wallet'; import { selectIsFetchingTxos } from 'redux/selectors/wallet';
import TransactionListTable from './view'; import TransactionListTable from './view';
const select = (state) => ({ const select = (state) => ({
rewards: selectClaimedRewardsByTransactionId(state),
loading: selectIsFetchingTxos(state), loading: selectIsFetchingTxos(state),
}); });

View file

@ -9,12 +9,11 @@ type Props = {
emptyMessage: ?string, emptyMessage: ?string,
loading: boolean, loading: boolean,
openModal: (id: string, { tx: Txo, cb: (string) => void }) => void, openModal: (id: string, { tx: Txo, cb: (string) => void }) => void,
rewards: {},
txos: Array<Txo>, txos: Array<Txo>,
}; };
function TransactionListTable(props: Props) { function TransactionListTable(props: Props) {
const { emptyMessage, rewards, loading, txos } = props; const { emptyMessage, loading, txos } = props;
const REVOCABLE_TYPES = ['channel', 'stream', 'repost', 'support', 'claim', 'collection']; const REVOCABLE_TYPES = ['channel', 'stream', 'repost', 'support', 'claim', 'collection'];
function revokeClaim(tx: any, cb: (string) => void) { function revokeClaim(tx: any, cb: (string) => void) {
props.openModal(MODALS.CONFIRM_CLAIM_REVOKE, { tx, cb }); props.openModal(MODALS.CONFIRM_CLAIM_REVOKE, { tx, cb });
@ -48,7 +47,6 @@ function TransactionListTable(props: Props) {
<TxoListItem <TxoListItem
key={`${t.txid}:${t.nout}-${i}`} key={`${t.txid}:${t.nout}-${i}`}
txo={t} txo={t}
reward={rewards && rewards[t.txid]}
isRevokeable={t.is_my_output && !t.is_spent && REVOCABLE_TYPES.includes(t.type)} isRevokeable={t.is_my_output && !t.is_spent && REVOCABLE_TYPES.includes(t.type)}
revokeClaim={revokeClaim} revokeClaim={revokeClaim}
/> />

View file

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

View file

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

View file

@ -1,11 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doClearEmailEntry, doUserSignUp } from 'redux/actions/user'; // new sync stuff
import {
selectEmailNewIsPending,
selectEmailNewErrorMessage,
selectEmailAlreadyExists,
selectUser,
} from 'redux/selectors/user';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import * as DAEMON_SETTINGS from 'constants/daemon_settings'; import * as DAEMON_SETTINGS from 'constants/daemon_settings';
import { doSetWalletSyncPreference, doSetDaemonSetting } from 'redux/actions/settings'; import { doSetWalletSyncPreference, doSetDaemonSetting } from 'redux/actions/settings';
@ -13,20 +7,14 @@ import { selectDaemonSettings, makeSelectClientSetting } from 'redux/selectors/s
import UserEmailNew from './view'; import UserEmailNew from './view';
const select = (state) => ({ const select = (state) => ({
isPending: selectEmailNewIsPending(state),
errorMessage: selectEmailNewErrorMessage(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state), syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
daemonSettings: selectDaemonSettings(state), daemonSettings: selectDaemonSettings(state),
emailExists: selectEmailAlreadyExists(state),
user: selectUser(state),
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({
setSync: (value) => dispatch(doSetWalletSyncPreference(value)), setSync: (value) => dispatch(doSetWalletSyncPreference(value)),
setShareDiagnosticData: (shouldShareData) => setShareDiagnosticData: (shouldShareData) =>
dispatch(doSetDaemonSetting(DAEMON_SETTINGS.SHARE_USAGE_DATA, 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); export default connect(select, perform)(UserEmailNew);

View file

@ -1,102 +1,55 @@
// @flow // @flow
import * as PAGES from 'constants/pages'; /*
import { DOMAIN } from 'config'; Saving this component for sign in/up
import React, { useState } from 'react'; */
import React from 'react';
import { FormField, Form } from 'component/common/form'; import { FormField, Form } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import analytics from 'analytics';
import { EMAIL_REGEX } from 'constants/email';
import I18nMessage from 'component/i18nMessage'; import I18nMessage from 'component/i18nMessage';
import { useHistory } from 'react-router-dom';
import Card from 'component/common/card'; import Card from 'component/common/card';
import ErrorText from 'component/common/error-text'; import ErrorText from 'component/common/error-text';
import Nag from 'component/common/nag'; import Nag from 'component/common/nag';
import classnames from 'classnames'; import classnames from 'classnames';
type Props = { type Props = {
errorMessage: ?string, // new sync stuff
emailExists: boolean,
isPending: boolean,
syncEnabled: boolean, syncEnabled: boolean,
setSync: (boolean) => void, setSync: (boolean) => void,
balance: number, balance: number,
daemonSettings: { share_usage_data: boolean }, daemonSettings: { share_usage_data: boolean },
setShareDiagnosticData: (boolean) => void, 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) { function UserEmailNew(props: Props) {
const { const [email, setEmail] = React.useState();
errorMessage, const [password, setPassword] = React.useState();
isPending, // const [server, setServer] = React.useState();
doSignUp, // const [errormessage, setErrorMessage] = React.useState();
setSync, const [mode, setMode] = React.useState(SIGN_UP_MODE);
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);
function handleUsageDataChange() { // const shareUsageData = false;
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 handleSubmit = () => {};
return ( return (
<div className={classnames('main__sign-up')}> <div className={classnames('main__sign-up')}>
<Card <Card
title={__('Cloud Connect')} title={__('Cloud Connect')}
subtitle={__('Connect your wallet to Odysee')} subtitle={__('Connect your wallet to Odysee')}
actions={ actions={
<div className={classnames({ 'card--disabled': DOMAIN === 'lbry.tv' && IS_WEB })}> <div>
<Form onSubmit={handleSubmit} className="section"> <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 <FormField
autoFocus autoFocus
placeholder={__('yourstruly@example.com')} placeholder={__('yourstruly@example.com')}
@ -113,45 +66,13 @@ function UserEmailNew(props: Props) {
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} 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"> <div className="section__actions">
<Button button="primary" type="submit" label={__('Sign Up')} disabled={!email || !password} />
<Button <Button
button="primary" button="link"
type="submit" onClick={setMode(mode === SIGN_UP_MODE ? SIGN_IN_MODE : SIGN_UP_MODE)}
label={__('Sign Up')} label={__('Log In')}
disabled={
!email || !password || !valid || (!IS_WEB && !localShareUsageData && !shareUsageData) || isPending
}
/> />
<Button button="link" onClick={handleChangeToSignIn} label={__('Log In')} />
</div> </div>
<p className="help--card-actions"> <p className="help--card-actions">
<I18nMessage <I18nMessage
@ -165,7 +86,7 @@ function UserEmailNew(props: Props) {
</Form> </Form>
</div> </div>
} }
nag={<>{errorMessage && <Nag type="error" relative message={<ErrorText>{errorMessage}</ErrorText>} />}</>} nag={<>{'someMessage' && <Nag type="error" relative message={<ErrorText>{'someMessage'}</ErrorText>} />}</>}
/> />
</div> </div>
); );

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUser, selectEmailToVerify } from 'redux/selectors/user';
import { selectCreatingChannel, selectMyChannelClaims, selectCreateChannelError } from 'redux/selectors/claims'; import { selectCreatingChannel, selectMyChannelClaims, selectCreateChannelError } from 'redux/selectors/claims';
import { doCreateChannel } from 'redux/actions/claims'; import { doCreateChannel } from 'redux/actions/claims';
import UserFirstChannel from './view'; import UserFirstChannel from './view';
const select = (state) => ({ const select = (state) => ({
email: selectEmailToVerify(state),
user: selectUser(state),
channels: selectMyChannelClaims(state), channels: selectMyChannelClaims(state),
creatingChannel: selectCreatingChannel(state), creatingChannel: selectCreatingChannel(state),
createChannelError: selectCreateChannelError(state), createChannelError: selectCreateChannelError(state),

View file

@ -15,22 +15,12 @@ type Props = {
creatingChannel: boolean, creatingChannel: boolean,
createChannelError: string, createChannelError: string,
claimingReward: boolean, claimingReward: boolean,
user: User,
doToggleInterestedInYoutubeSync: () => void, doToggleInterestedInYoutubeSync: () => void,
}; };
function UserFirstChannel(props: Props) { function UserFirstChannel(props: Props) {
const { const { createChannel, creatingChannel, claimingReward, createChannelError, doToggleInterestedInYoutubeSync } = props;
createChannel, const [channel, setChannel] = useState(''); // maybe recommend channel based on email when we have it
creatingChannel,
claimingReward,
user,
createChannelError,
doToggleInterestedInYoutubeSync,
} = props;
const { primary_email: primaryEmail } = user;
const initialChannel = primaryEmail ? primaryEmail.split('@')[0] : '';
const [channel, setChannel] = useState(initialChannel);
const [nameError, setNameError] = useState(undefined); const [nameError, setNameError] = useState(undefined);
function handleCreateChannel() { function handleCreateChannel() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,23 +6,15 @@ import {
} from 'redux/selectors/collections'; } from 'redux/selectors/collections';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import * as COLLECTIONS_CONSTS from 'constants/collections'; import * as COLLECTIONS_CONSTS from 'constants/collections';
import { import { doChangeVolume, doChangeMute, doAnalyticsView, doAnaltyicsPurchaseEvent } from 'redux/actions/app';
doChangeVolume,
doChangeMute,
doAnalyticsView,
doAnalyticsBuffer,
doAnaltyicsPurchaseEvent,
} from 'redux/actions/app';
import { selectVolume, selectMute } from 'redux/selectors/app'; import { selectVolume, selectMute } from 'redux/selectors/app';
import { savePosition, clearPosition, doPlayUri, doSetPlayingUri } from 'redux/actions/content'; import { savePosition, clearPosition, doPlayUri, doSetPlayingUri } from 'redux/actions/content';
import { makeSelectContentPositionForUri, makeSelectIsPlayerFloating, selectPlayingUri } from 'redux/selectors/content'; import { makeSelectContentPositionForUri, makeSelectIsPlayerFloating, selectPlayingUri } from 'redux/selectors/content';
import { selectRecommendedContentForUri } from 'redux/selectors/search'; import { selectRecommendedContentForUri } from 'redux/selectors/search';
import VideoViewer from './view'; import VideoViewer from './view';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
import { selectDaemonSettings, makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings'; import { selectDaemonSettings, makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
import { toggleVideoTheaterMode, toggleAutoplayNext, doSetClientSetting } from 'redux/actions/settings'; import { toggleVideoTheaterMode, toggleAutoplayNext, doSetClientSetting } from 'redux/actions/settings';
import { selectUser } from 'redux/selectors/user';
const select = (state, props) => { const select = (state, props) => {
const { search } = props.location; const { search } = props.location;
@ -30,7 +22,6 @@ const select = (state, props) => {
const uri = props.uri; const uri = props.uri;
// TODO: eventually this should be received from DB and not local state (https://github.com/lbryio/lbry-desktop/issues/6796) // 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 position = urlParams.get('t') !== null ? urlParams.get('t') : makeSelectContentPositionForUri(uri)(state);
const userId = selectUser(state) && selectUser(state).id;
const playingUri = selectPlayingUri(state); const playingUri = selectPlayingUri(state);
const collectionId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID) || (playingUri && playingUri.collectionId); const collectionId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID) || (playingUri && playingUri.collectionId);
const isMarkdownOrComment = playingUri && (playingUri.source === 'markdown' || playingUri.source === 'comment'); const isMarkdownOrComment = playingUri && (playingUri.source === 'markdown' || playingUri.source === 'comment');
@ -47,7 +38,6 @@ const select = (state, props) => {
return { return {
position, position,
userId,
collectionId, collectionId,
nextRecommendedUri, nextRecommendedUri,
previousListUri, previousListUri,
@ -71,8 +61,6 @@ const perform = (dispatch) => ({
clearPosition: (uri) => dispatch(clearPosition(uri)), clearPosition: (uri) => dispatch(clearPosition(uri)),
changeMute: (muted) => dispatch(doChangeMute(muted)), changeMute: (muted) => dispatch(doChangeMute(muted)),
doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)), doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
doAnalyticsBuffer: (uri, bufferData) => dispatch(doAnalyticsBuffer(uri, bufferData)),
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
toggleVideoTheaterMode: () => dispatch(toggleVideoTheaterMode()), toggleVideoTheaterMode: () => dispatch(toggleVideoTheaterMode()),
toggleAutoplayNext: () => dispatch(toggleAutoplayNext()), toggleAutoplayNext: () => dispatch(toggleAutoplayNext()),
setVideoPlaybackRate: (rate) => dispatch(doSetClientSetting(SETTINGS.VIDEO_PLAYBACK_RATE, rate)), setVideoPlaybackRate: (rate) => dispatch(doSetClientSetting(SETTINGS.VIDEO_PLAYBACK_RATE, rate)),

View file

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