diff --git a/app/assets/stylesheets/admin/tooltip.scss b/app/assets/stylesheets/admin/tooltip.scss index 12f3b160de..ebcb695856 100644 --- a/app/assets/stylesheets/admin/tooltip.scss +++ b/app/assets/stylesheets/admin/tooltip.scss @@ -18,7 +18,7 @@ /* Position tooltip above the element */ [data-tooltip]:before { position: absolute; - bottom: 150%; + bottom: 100%; left: 50%; margin-bottom: 5px; margin-left: -80px; @@ -36,7 +36,7 @@ /* Triangle hack to make tooltip look like a speech bubble */ [data-tooltip]:after { position: absolute; - bottom: 150%; + bottom: 100%; left: 50%; margin-left: -5px; width: 0; diff --git a/app/assets/stylesheets/pages/home.scss b/app/assets/stylesheets/pages/home.scss index c78e8cc7cf..92c4e629df 100644 --- a/app/assets/stylesheets/pages/home.scss +++ b/app/assets/stylesheets/pages/home.scss @@ -209,17 +209,14 @@ } &-alert { - width: 60%; - #promo-panel-alert { font-size: 14px; font-weight: 600; color: #006192; padding: 30px 30px 35px 70px; margin-right: 20px; - background-color: white; + background-color: rgba(255, 255, 255, 0.6); text-align: left; - opacity: 0.5; border-radius: 10px; background-image: url(asset-path("icn-info.png")); background-position: center left 15px; diff --git a/app/controllers/admin/promo_registrations_controller.rb b/app/controllers/admin/promo_registrations_controller.rb new file mode 100644 index 0000000000..482114aaf3 --- /dev/null +++ b/app/controllers/admin/promo_registrations_controller.rb @@ -0,0 +1,8 @@ +class Admin::PromoRegistrationsController < AdminController + def for_referral_code + publisher = Publisher.find(params[:publisher_id]) + promo_registration = publisher.promo_registrations.find_by(referral_code: params[:referral_code]) + render :unauthorized and return if promo_registration.nil? + render json: promo_registration.stats_by_date.to_json + end +end diff --git a/app/controllers/admin/publishers/referrals_controller.rb b/app/controllers/admin/publishers/referrals_controller.rb new file mode 100644 index 0000000000..e0026c7292 --- /dev/null +++ b/app/controllers/admin/publishers/referrals_controller.rb @@ -0,0 +1,5 @@ +class Admin::Publishers::ReferralsController < Admin::PublishersController + def show + @navigation_view = Views::Admin::NavigationView.new(@publisher).as_json.merge({ navbarSelection: "Referrals" }).to_json + end +end diff --git a/app/controllers/admin/publishers_controller.rb b/app/controllers/admin/publishers_controller.rb index 5523907b69..8bee816f25 100644 --- a/app/controllers/admin/publishers_controller.rb +++ b/app/controllers/admin/publishers_controller.rb @@ -44,6 +44,7 @@ def show @publisher = Publisher.find(params[:id]) @navigation_view = Views::Admin::NavigationView.new(@publisher).as_json.merge({ navbarSelection: "Dashboard" }).to_json @potential_referral_payment = @publisher.most_recent_potential_referral_payment + @referral_owner_status = Promo::Client.new.owner_state.find(id: params[:id]) @current_user = current_user end diff --git a/app/controllers/admin/referrals_controller.rb b/app/controllers/admin/referrals_controller.rb deleted file mode 100644 index 18fff85f0b..0000000000 --- a/app/controllers/admin/referrals_controller.rb +++ /dev/null @@ -1,44 +0,0 @@ -module Admin - class ReferralsController < AdminController - include PromosHelper - include PublishersHelper - def show - publisher = Publisher.find(params[:id]) - @navigation_view = Views::Admin::NavigationView.new(publisher).as_json.merge({ navbarSelection: "Referrals" }).to_json - respond_to do |format| - format.html do - @data = show_data(params[:id]) - end - format.json do - render json: show_data(params[:id]) - end - end - end - - def show_data(id) - publisher = Publisher.find(id) - promo_registrations = [] - - publisher.promo_registrations.each do |promo_registration| - stats = [] - JSON.parse(promo_registration.stats).each do |stat| - date = Date.parse(stat["ymd"]).strftime("%m/%d/%Y") - stats.push({ - date: date, - downloads: stat["retrievals"], - installs: stat["first_runs"], - confirmations: stat["finalized"], - }) - end - promo_registrations.push({ - referralCode: promo_registration.referral_code, - stats: stats, - }) - end - - { - referralCodes: promo_registrations, - }.merge(Views::Admin::NavigationView.new(publisher).as_json) - end - end -end diff --git a/app/controllers/promo_registrations_controller.rb b/app/controllers/promo_registrations_controller.rb index 970a8fea1b..0dedf59023 100644 --- a/app/controllers/promo_registrations_controller.rb +++ b/app/controllers/promo_registrations_controller.rb @@ -15,7 +15,7 @@ def create if @publisher_has_verified_channel if @publisher.channels.where(verified: true).count > 5 - RegisterPublisherForPromoJob.perform_later(publisher: @publisher) + Promo::RegisterPublisherForPromoJob.perform_later(publisher: @publisher) redirect_to home_publishers_path, notice: t("promo.activated.please_wait") else Promo::PublisherChannelsRegistrar.new(publisher: @publisher).perform diff --git a/app/controllers/publishers/promo_registrations_controller.rb b/app/controllers/publishers/promo_registrations_controller.rb index 2da15d8080..0b6f9f2759 100644 --- a/app/controllers/publishers/promo_registrations_controller.rb +++ b/app/controllers/publishers/promo_registrations_controller.rb @@ -1,6 +1,8 @@ class Publishers::PromoRegistrationsController < PublishersController def for_referral_code - promo_registration = current_publisher.promo_registrations.find_by(referral_code: params[:referral_code]) + promo_registration = current_publisher.admin? ? + PromoRegistration.find_by(referral_code: params[:referral_code]) : + current_publisher.promo_registrations.find_by(referral_code: params[:referral_code]) render :unauthorized and return if promo_registration.nil? render json: promo_registration.stats_by_date.to_json end diff --git a/app/helpers/publishers_helper.rb b/app/helpers/publishers_helper.rb index d2b28a0456..840ade8438 100644 --- a/app/helpers/publishers_helper.rb +++ b/app/helpers/publishers_helper.rb @@ -39,8 +39,15 @@ def publisher_overall_bat_balance(publisher) balance = I18n.t("helpers.publisher.balance_unavailable") sentry_catcher do publisher = publisher.become_subclass - amount = publisher.wallet&.overall_balance&.amount_bat - amount = publisher.balance if publisher.partner? + + if publisher.partner? + amount = publisher.balance + elsif publisher.only_user_funds? + amount = publisher.wallet&.contribution_balance&.amount_bat + else + amount = publisher.wallet&.overall_balance&.amount_bat + end + balance = '%.2f' % amount if amount.present? end @@ -51,8 +58,14 @@ def publisher_converted_overall_balance(publisher) return if publisher.default_currency == "BAT" || publisher.default_currency.blank? publisher = publisher&.become_subclass - balance = publisher.wallet&.overall_balance&.amount_default_currency - balance = publisher.balance_in_currency if publisher.partner? + + if publisher.partner? + balance = publisher.balance_in_currency + elsif publisher.only_user_funds? + balance = publisher.wallet&.contribution_balance&.amount_default_currency + else + balance = publisher.wallet&.overall_balance&.amount_default_currency + end if balance.present? I18n.t("helpers.publisher.balance_pending_approximate", diff --git a/app/javascript/packs/chart/Chart.js b/app/javascript/packs/chart/Chart.js index 540a15ab6b..82522a923f 100644 --- a/app/javascript/packs/chart/Chart.js +++ b/app/javascript/packs/chart/Chart.js @@ -2,7 +2,7 @@ import * as React from "react"; import moment from "moment"; import Chart from "chart.js"; -class ReactChart extends React.Component { +export default class ReactChart extends React.Component { constructor(props) { super(props); this.chartRef = React.createRef(); @@ -40,10 +40,7 @@ class ReactChart extends React.Component { }); } else { this.chart.data = this.getData(this.props.data); - this.chart.options = this.getOptions( - this.props.title, - this.getSuggestedMax(this.props.data) - ); + this.chart.options = this.getOptions(this.props.title, this.getSuggestedMax(this.props.data)); this.chart.update(); } } @@ -128,10 +125,8 @@ class ReactChart extends React.Component { var currentMax = 0; Object.keys(data).forEach(function(key) { var value = data[key]; - currentMax = - value.retrievals > currentMax ? value.retrievals : currentMax; - currentMax = - value.first_runs > currentMax ? value.first_runs : currentMax; + currentMax = value.retrievals > currentMax ? value.retrievals : currentMax; + currentMax = value.first_runs > currentMax ? value.first_runs : currentMax; currentMax = value.finalized > currentMax ? value.finalized : currentMax; }); return Math.ceil((currentMax / 95) * 100); @@ -154,12 +149,6 @@ class ReactChart extends React.Component { } render() { - return ( -
- {this.props.data && } -
- ); + return
{this.props.data && }
; } } - -export default ReactChart; diff --git a/app/javascript/packs/referral_charts.jsx b/app/javascript/packs/referral_charts.jsx index e7634fdc39..79656e1c03 100644 --- a/app/javascript/packs/referral_charts.jsx +++ b/app/javascript/packs/referral_charts.jsx @@ -1,13 +1,12 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; -import "babel-polyfill"; import styled from "brave-ui/theme"; import Select from "brave-ui/components/formControls/select"; import ControlWrapper from "brave-ui/components/formControls/controlWrapper"; import { PrimaryButton } from "../publishers/ReferralChartsStyle"; // import "../publishers/dashboard_chart"; import routes from "../views/routes"; -import Chart from "./chart/Chart"; +import ReactChart from "./chart/Chart"; import { ThemeProvider } from "brave-ui/theme"; import Theme from "brave-ui/theme/brave-default"; @@ -27,10 +26,11 @@ export default class ReferralCharts extends React.Component { async viewReferralCodeStats() { const node = this.selectMenuRef.current; - var url = routes.publishers.promo_registrations.show.path.replace( - "{id}", - this.props.publisherId - ); + var url = + this.props.scope === "admin" + ? routes.admin.promo_registrations.show.path.replace("{publisher_id}", this.props.publisherId) + : routes.publishers.promo_registrations.show.path.replace("{id}", this.props.publisherId); + url = url.replace("{referral_code}", node.state.value); const result = await fetch(url, { headers: { @@ -71,13 +71,13 @@ export default class ReferralCharts extends React.Component { View stats - + ); } } -export function renderReferralCharts() { +export function renderReferralCharts(scope) { const { value } = document.getElementById("referrals-hidden-tags"); const publisherId = document.getElementById("publisher_id").value; if (value === undefined) { @@ -85,7 +85,8 @@ export function renderReferralCharts() { } let referralCodes = JSON.parse(value); let props = { - referralCodes: referralCodes + referralCodes: referralCodes, + scope: scope }; ReactDOM.render( , diff --git a/app/javascript/packs/views/admin/referrals/Referrals.jsx b/app/javascript/packs/views/admin/referrals/Referrals.jsx index ee9dedecbc..d0c794e367 100644 --- a/app/javascript/packs/views/admin/referrals/Referrals.jsx +++ b/app/javascript/packs/views/admin/referrals/Referrals.jsx @@ -1,14 +1,7 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; -import Referrals from "../../../../views/admin/referrals/Referrals"; +import { renderReferralCharts } from "../../../../packs/referral_charts"; document.addEventListener("DOMContentLoaded", () => { - const node = document.getElementById("referrals_data"); - const data = JSON.parse(node.getAttribute("data")); - ReactDOM.render( - , - document - .getElementById("main-content") - .appendChild(document.createElement("div")) - ); + renderReferralCharts("admin"); }); diff --git a/app/javascript/packs/views/referrals/Referrals.jsx b/app/javascript/packs/views/referrals/Referrals.jsx index 2b80642704..a002fddabe 100644 --- a/app/javascript/packs/views/referrals/Referrals.jsx +++ b/app/javascript/packs/views/referrals/Referrals.jsx @@ -3,10 +3,5 @@ import * as ReactDOM from "react-dom"; import Referrals from "../../../views/referrals/Referrals"; document.addEventListener("DOMContentLoaded", () => { - ReactDOM.render( - , - document.body.appendChild( - document.getElementsByClassName("main-content")[0] - ) - ); + ReactDOM.render(, document.body.appendChild(document.getElementsByClassName("main-content")[0])); }); diff --git a/app/javascript/publishers/dashboard_chart.js b/app/javascript/publishers/dashboard_chart.js deleted file mode 100644 index 5f2c547736..0000000000 --- a/app/javascript/publishers/dashboard_chart.js +++ /dev/null @@ -1,91 +0,0 @@ -import Chart from 'chart.js'; -import moment from 'moment'; - -let colors = [ - '255, 99, 132', - '54, 162, 235', - '255, 206, 86', - '75, 192, 192', - '153, 102, 255', - '255, 159, 64' -] - -function createLabels(startingDate) { - // https://stackoverflow.com/questions/7556591/is-the-javascript-date-object-always-one-day-off - var loop = new Date(startingDate.replace(/-/g, '\/')); - var dates_array = []; - - while (loop <= new Date()) { - dates_array.push(loop.getFullYear() + '-' + (loop.getMonth() + 1) + '-' + loop.getDate()); - loop.setDate(loop.getDate() + 1); - } - - return dates_array; -} - -// Max of the chart is 80% of the suggested max to be used by Chartjs -function getSuggestedMax(data) { - var currentMax = 0; - Object.keys(data).forEach(function (key) { - var value = data[key]; - currentMax = value.retrievals > currentMax ? value.retrievals : currentMax; - currentMax = value.first_runs > currentMax ? value.first_runs : currentMax; - currentMax = value.finalized > currentMax ? value.finalized : currentMax; - }); - return (currentMax * 100 / 95) -} - -function createChart(data, title, suggestedMax) { - var wrapper = document.getElementById('channel-referrals-stats-chart'); - var canvas = document.getElementById('channel-referrals-stats-chart-canvas'); - if (!canvas) { - canvas = document.createElement('canvas'); - canvas.setAttribute('id', 'channel-referrals-stats-chart-canvas'); - canvas.setAttribute("width", "400"); - canvas.setAttribute("height", "300"); - wrapper.appendChild(canvas); - } - - Chart.defaults.global.defaultFontFamily = 'Poppins'; - - new Chart(canvas, { - type: 'line', - data: { - labels: createLabels(data[0]['ymd']), - datasets: [ - { - label: 'Downloads', - data: data.map(x => x.retrievals), - borderColor: '#F88469', - }, - { - label: 'Installs', - data: data.map(x => x.first_runs), - borderColor: '#7B82E1', - }, - { - label: '30-Day-Use', - data: data.map(x => x.finalized), - borderColor: '#66C3FC', - }, - ] - }, - options: { - tooltips: { - mode: 'x' - }, - title: { - fontSize: 18, - display: true, - text: title.toUpperCase() - }, - scales: { - yAxes: [{ - ticks: { - suggestedMax: suggestedMax - } - }] - } - } - }); -} diff --git a/app/javascript/publishers/home.js b/app/javascript/publishers/home.js index 947b927961..1c49b6c202 100644 --- a/app/javascript/publishers/home.js +++ b/app/javascript/publishers/home.js @@ -43,14 +43,9 @@ function updateOverallBalance(balance) { batAmount.innerText = formatAmount(balance.amount_bat); let convertedAmount = document.getElementById("converted_amount"); - if ( - !(balance.default_currency === "BAT" || balance.default_currency === null) - ) { + if (!(balance.default_currency === "BAT" || balance.default_currency === null)) { convertedAmount.style.display = "block"; - convertedAmount.innerText = formatConvertedBalance( - balance.amount_default_currency, - balance.default_currency - ); + convertedAmount.innerText = formatConvertedBalance(balance.amount_default_currency, balance.default_currency); } } @@ -58,22 +53,17 @@ function updateLastSettlement(lastSettlementBalance) { let lastSettlement = document.getElementById("last_settlement"); let lastDepositDate = document.getElementById("last_deposit_date"); let lastDepositBatAmount = document.getElementById("last_deposit_bat_amount"); - let lastDepositConvertedAmount = document.getElementById( - "last_deposit_converted_amount" - ); + let lastDepositConvertedAmount = document.getElementById("last_deposit_converted_amount"); if (lastSettlementBalance.timestamp) { lastSettlement.classList.remove("no-settlement-made"); lastSettlement.classList.add("settlement-made"); - lastDepositDate.innerText = formatFullDate( - new Date(lastSettlementBalance.timestamp * 1000) - ); // Convert to milliseconds + lastDepositDate.innerText = formatFullDate(new Date(lastSettlementBalance.timestamp * 1000)); // Convert to milliseconds lastDepositBatAmount.innerText = lastSettlementBalance.amount_bat; lastDepositConvertedAmount.style.display = - lastSettlementBalance.settlement_currency === "BAT" || - lastSettlementBalance.settlement_currency === null + lastSettlementBalance.settlement_currency === "BAT" || lastSettlementBalance.settlement_currency === null ? "none" : "block"; lastDepositConvertedAmount.innerText = formatConvertedBalance( @@ -92,43 +82,30 @@ function updateLastSettlement(lastSettlementBalance) { function updateChannelBalances(wallet) { for (let channelId in wallet.channelBalances) { - let channelAmount = document.getElementById( - "channel_amount_bat_" + channelId - ); + let channelAmount = document.getElementById("channel_amount_bat_" + channelId); if (channelAmount) { - channelAmount.innerText = formatAmount( - wallet.channelBalances[channelId].amount_bat - ); + channelAmount.innerText = formatAmount(wallet.channelBalances[channelId].amount_bat); } } } function updateDefaultCurrencyValue(wallet) { let upholdStatusElement = document.getElementById("uphold_status"); - upholdStatusElement.setAttribute( - "data-default-currency", - wallet.defaultCurrency || "" - ); + upholdStatusElement.setAttribute("data-default-currency", wallet.defaultCurrency || ""); let defaultCurrencyDisplay = document.getElementById("default_currency_code"); - defaultCurrencyDisplay.innerText = - wallet.defaultCurrency || NO_CURRENCY_SELECTED; + defaultCurrencyDisplay.innerText = wallet.defaultCurrency || NO_CURRENCY_SELECTED; } function updatePossibleCurrencies(wallet) { let possibleCurrencies = wallet.possibleCurrencies; let upholdStatusElement = document.getElementById("uphold_status"); - upholdStatusElement.setAttribute( - "data-possible-currencies", - JSON.stringify(possibleCurrencies) - ); + upholdStatusElement.setAttribute("data-possible-currencies", JSON.stringify(possibleCurrencies)); } function getPossibleCurrencies() { let upholdStatusElement = document.getElementById("uphold_status"); - return JSON.parse( - upholdStatusElement.getAttribute("data-possible-currencies") - ); + return JSON.parse(upholdStatusElement.getAttribute("data-possible-currencies")); } function populateCurrencySelect(select, possibleCurrencies, selectedCurrency) { @@ -146,10 +123,7 @@ function populateCurrencySelect(select, possibleCurrencies, selectedCurrency) { let option = document.createElement("option"); option.value = currency; option.innerHTML = currency; - if ( - (!selectedCurrency || selectedCurrency.length === 0) && - currency === BASIC_ATTENTION_TOKEN - ) { + if ((!selectedCurrency || selectedCurrency.length === 0) && currency === BASIC_ATTENTION_TOKEN) { option.selected = true; } else { option.selected = currency === selectedCurrency; @@ -195,20 +169,14 @@ function refreshBalance() { } function removeChannel(channelId) { - submitForm("remove_channel_" + channelId, "DELETE", true).then(function( - response - ) { + submitForm("remove_channel_" + channelId, "DELETE", true).then(function(response) { let channelRow = document.getElementById("channel_row_" + channelId); channelRow.classList.add("channel-hidden"); // Show channel placeholder if no channels are still visible - let visibleChannelRows = document.querySelectorAll( - "div.channel-row:not(.channel-hidden)" - ); + let visibleChannelRows = document.querySelectorAll("div.channel-row:not(.channel-hidden)"); if (visibleChannelRows.length === 0) { - let addChannelPlaceholder = document.getElementById( - "add_channel_placeholder" - ); + let addChannelPlaceholder = document.getElementById("add_channel_placeholder"); addChannelPlaceholder.classList.remove("hidden"); } flash.clear(); @@ -237,12 +205,8 @@ function checkUpholdStatus() { }) .then(function(body) { let upholdStatus = document.getElementById("uphold_status"); - let upholdStatusSummary = document.querySelector( - "#uphold_status_display .status-summary .text" - ); - let upholdStatusDescription = document.querySelector( - "#uphold_connect .status-description" - ); + let upholdStatusSummary = document.querySelector("#uphold_status_display .status-summary .text"); + let upholdStatusDescription = document.querySelector("#uphold_connect .status-description"); let timedOut = checkUpholdStatusCount >= 15; if (timedOut) { @@ -250,8 +214,7 @@ function checkUpholdStatus() { body = { uphold_status_class: "uphold-timeout", uphold_status_summary: "Connection problems", - uphold_status_description: - "We are experiencing communication problems. Please check back later." + uphold_status_description: "We are experiencing communication problems. Please check back later." }; } @@ -270,9 +233,7 @@ function checkUpholdStatus() { if ( checkUpholdStatusInterval != null && - (timedOut || - body.uphold_status === "verified" || - body.uphold_status === "restricted") + (timedOut || body.uphold_status === "verified" || body.uphold_status === "restricted") ) { hideReconnectButton(); @@ -306,26 +267,16 @@ function disconnectUphold() { } function openDefaultCurrencyModal() { - let template = document.querySelector( - "#confirm_default_currency_modal_wrapper" - ); + let template = document.querySelector("#confirm_default_currency_modal_wrapper"); let closeFn = openModal(template.innerHTML); let form = document.getElementById("confirm_default_currency_form"); // Sync default currency selected in modal with options and value from dashboard let upholdStatusElement = document.getElementById("uphold_status"); - let currentDefaultCurrency = upholdStatusElement.getAttribute( - "data-default-currency" - ); - let currencySelectInModal = document.getElementById( - "publisher_default_currency" - ); - populateCurrencySelect( - currencySelectInModal, - getPossibleCurrencies(), - currentDefaultCurrency || "" - ); + let currentDefaultCurrency = upholdStatusElement.getAttribute("data-default-currency"); + let currencySelectInModal = document.getElementById("publisher_default_currency"); + populateCurrencySelect(currencySelectInModal, getPossibleCurrencies(), currentDefaultCurrency || ""); form.addEventListener( "submit", @@ -333,9 +284,7 @@ function openDefaultCurrencyModal() { event.preventDefault(); let modal = document.getElementById("confirm_default_currency_modal"); - let status = document.querySelector( - "#confirm_default_currency_modal .status" - ); + let status = document.querySelector("#confirm_default_currency_modal .status"); if (!currencySelectInModal.value) { closeFn(); @@ -378,11 +327,7 @@ function toggleDialog(event, elements) { // Do not hide if the clicked element is supposed to show the bubble // Or if the clicked element is the bubble let e = elements[i]; - if ( - e === event.target || - e.nextSibling == event.target || - e.nextSibling.firstChild == event.target - ) { + if (e === event.target || e.nextSibling == event.target || e.nextSibling.firstChild == event.target) { continue; } else { hideVerificationFailureWhatHappened(e); @@ -399,11 +344,7 @@ document.addEventListener("DOMContentLoaded", function() { if (upholdStatusElement.classList.contains("uphold-processing")) { checkUpholdStatusInterval = window.setInterval(checkUpholdStatus, 2000); - } else if ( - upholdStatusElement.getAttribute( - "data-open-confirm-default-currency-modal" - ) === "true" - ) { + } else if (upholdStatusElement.getAttribute("data-open-confirm-default-currency-modal") === "true") { openDefaultCurrencyModal(); } @@ -413,9 +354,7 @@ document.addEventListener("DOMContentLoaded", function() { "click", function(event) { let channelId = event.target.getAttribute("data-channel-id"); - let template = document.querySelector( - '[data-js-channel-removal-confirmation-template="' + channelId + '"]' - ); + let template = document.querySelector('[data-js-channel-removal-confirmation-template="' + channelId + '"]'); openModal( template.innerHTML, function() { @@ -457,9 +396,7 @@ document.addEventListener("DOMContentLoaded", function() { false ); - let changeDefaultCurrencyLink = document.getElementById( - "change_default_currency" - ); + let changeDefaultCurrencyLink = document.getElementById("change_default_currency"); if (changeDefaultCurrencyLink) { changeDefaultCurrencyLink.addEventListener( "click", @@ -485,25 +422,15 @@ document.addEventListener("DOMContentLoaded", function() { let editContact = document.getElementById("edit_contact"); let cancelEditContact = document.getElementById("cancel_edit_contact"); - let verificationFailureWhatHappenedElements = document.getElementsByClassName( - "verification-failed--what-happened" - ); + let verificationFailureWhatHappenedElements = document.getElementsByClassName("verification-failed--what-happened"); for (let i = 0; i < verificationFailureWhatHappenedElements.length; i++) { - verificationFailureWhatHappenedElements[i].addEventListener( - "click", - showWhatHappenedVerificationFailure, - false - ); + verificationFailureWhatHappenedElements[i].addEventListener("click", showWhatHappenedVerificationFailure, false); } let infoText = document.getElementsByClassName("info--what-happened"); for (let i = 0; i < infoText.length; i++) { - infoText[i].addEventListener( - "click", - showWhatHappenedVerificationFailure, - false - ); + infoText[i].addEventListener("click", showWhatHappenedVerificationFailure, false); } // Hide all verification failed bubbles when anywhere on DOM is clicked @@ -512,16 +439,13 @@ document.addEventListener("DOMContentLoaded", function() { toggleDialog(event, infoText); }); - let instantDonationButton = document.getElementById( - "instant-donation-button" - ); + let instantDonationButton = document.getElementById("instant-donation-button"); editContact.addEventListener( "click", function(event) { updateContactName.value = showContactName.innerText; - updateContactEmail.value = - pendingContactEmail.innerText || showContactEmail.innerText; + updateContactEmail.value = pendingContactEmail.innerText || showContactEmail.innerText; showContact.classList.add("hidden"); updateContactForm.classList.remove("hidden"); editContact.classList.add("hidden"); @@ -547,10 +471,8 @@ document.addEventListener("DOMContentLoaded", function() { async function(event) { document.getElementById("intro-container").style.padding = "50px"; document.getElementsByClassName("modal-panel")[0].style.padding = "0px"; - document.getElementsByClassName("modal-panel--content")[0].style.padding = - "0px"; - let preferredCurrency = document.getElementById("preferred_currency") - .value; + document.getElementsByClassName("modal-panel--content")[0].style.padding = "0px"; + let preferredCurrency = document.getElementById("preferred_currency").value; let conversionRate = document.getElementById("conversion_rate").value; let url = "/publishers/get_site_banner_data"; @@ -561,8 +483,7 @@ document.addEventListener("DOMContentLoaded", function() { headers: { Accept: "text/html", "X-Requested-With": "XMLHttpRequest", - "X-CSRF-Token": document.head.querySelector("[name=csrf-token]") - .content + "X-CSRF-Token": document.head.querySelector("[name=csrf-token]").content } }; @@ -600,16 +521,10 @@ document.addEventListener("DOMContentLoaded", function() { ); }; - document.getElementsByClassName( - "modal-panel--close js-deny" - )[0].onclick = function(e) { - document.getElementsByClassName("modal-panel")[0].style.maxWidth = - "40rem"; - document.getElementsByClassName("modal-panel")[0].style.padding = - "2rem 2rem"; - document.getElementsByClassName( - "modal-panel--content" - )[0].style.padding = "1rem 1rem 0 1rem"; + document.getElementsByClassName("modal-panel--close js-deny")[0].onclick = function(e) { + document.getElementsByClassName("modal-panel")[0].style.maxWidth = "40rem"; + document.getElementsByClassName("modal-panel")[0].style.padding = "2rem 2rem"; + document.getElementsByClassName("modal-panel--content")[0].style.padding = "1rem 1rem 0 1rem"; }; }, false @@ -627,15 +542,11 @@ document.addEventListener("DOMContentLoaded", function() { showPendingContactEmail(updatedEmail); let currentUserName = document.querySelector(".js-current-user-name"); - let userNameDropDown = document.querySelector( - ".js-user-name-dropdown" - ); + let userNameDropDown = document.querySelector(".js-user-name-dropdown"); currentUserName.innerText = updateContactName.value; userNameDropDown.innerText = updateContactName.value; } else { - let pendingEmailNotice = document.getElementById( - "pending_email_notice" - ); + let pendingEmailNotice = document.getElementById("pending_email_notice"); pendingEmailNotice.innerHTML = "Unable to change email; the email address may be in use. Please enter a different email address."; pendingEmailNotice.classList.remove("hidden"); @@ -646,9 +557,7 @@ document.addEventListener("DOMContentLoaded", function() { editContact.classList.remove("hidden"); // Re-enable submit button to allow form to be resubmitted - let submitButton = updateContactForm.querySelector( - "input[type=submit][disabled]" - ); + let submitButton = updateContactForm.querySelector("input[type=submit][disabled]"); if (submitButton) { submitButton.removeAttribute("disabled"); submitButton.blur(); @@ -658,5 +567,5 @@ document.addEventListener("DOMContentLoaded", function() { false ); - renderReferralCharts(); + renderReferralCharts("publisher"); }); diff --git a/app/javascript/routes/routes.ts b/app/javascript/routes/routes.ts index 7c25cd2cb1..3dec08feb1 100644 --- a/app/javascript/routes/routes.ts +++ b/app/javascript/routes/routes.ts @@ -1,6 +1,7 @@ export default { admin: { userNavbar: { + // TODO: These routes aren't RESTful and should be fixed channels: { path: "/admin/channels/{id}" }, @@ -11,7 +12,7 @@ export default { path: "/admin/payments/{id}" }, referrals: { - path: "/admin/referrals/{id}" + path: "/admin/publishers/{id}/referrals" } } }, diff --git a/app/javascript/views/admin/referrals/Referrals.tsx b/app/javascript/views/admin/referrals/Referrals.tsx index 3471495a69..0da81e1bb4 100644 --- a/app/javascript/views/admin/referrals/Referrals.tsx +++ b/app/javascript/views/admin/referrals/Referrals.tsx @@ -1,8 +1,9 @@ import * as React from "react"; import { Cell, Container, Grid } from "../../../components/grid/Grid"; +import renderReferralCharts from "../../../packs/referral_charts"; import UserNavbar from "../components/userNavbar/UserNavbar"; -import ReferralsChart from "./components/referralsChart/ReferralsChart"; + import { templateAreas, templateRows } from "./ReferralsStyle"; export enum NavbarSelection { @@ -19,24 +20,20 @@ interface IReferralsProps { export default class Referrals extends React.Component { constructor(props) { super(props); + renderReferralCharts("admin"); } public render() { return ( - + - - - + diff --git a/app/javascript/views/admin/referrals/components/referralsChart/ReferralsChart.tsx b/app/javascript/views/admin/referrals/components/referralsChart/ReferralsChart.tsx deleted file mode 100644 index ac664bb63b..0000000000 --- a/app/javascript/views/admin/referrals/components/referralsChart/ReferralsChart.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import * as React from "react"; - -import Select from "brave-ui/components/formControls/select"; -import Chart from "chart.js"; -import Card from "../../../../../components/card/Card"; - -interface IReferralsChartProps { - referralCodes: any; -} - -interface IReferralsChartState { - selectedReferralCode: any; - downloadsToggle: any; - installsToggle: any; - confirmationsToggle: any; -} - -export default class Referrals extends React.Component< - IReferralsChartProps, - IReferralsChartState -> { - private node; - constructor(props) { - super(props); - this.state = { - confirmationsToggle: true, - downloadsToggle: true, - installsToggle: true, - selectedReferralCode: 0 - }; - } - - public componentDidMount() { - this.createReferralsChart( - this.props.referralCodes[this.state.selectedReferralCode] - ); - } - - public componentDidUpdate() { - this.createReferralsChart( - this.props.referralCodes[this.state.selectedReferralCode] - ); - } - - public handleReferralCodeSelect = e => { - this.setState({ selectedReferralCode: e.target.value }); - }; - - public handleDataSelect = index => { - switch (index) { - case 0: - this.setState(prevState => ({ - downloadsToggle: !prevState.downloadsToggle - })); - break; - case 1: - this.setState(prevState => ({ - installsToggle: !prevState.installsToggle - })); - break; - case 2: - this.setState(prevState => ({ - confirmationsToggle: !prevState.confirmationsToggle - })); - break; - } - }; - - public createReferralsChart(referralCode) { - const node = this.node; - - if (!referralCode) { - return; - } - const stats = referralCode.stats; - const chartLabels = []; - let downloads = []; - let installs = []; - let confirmations = []; - - stats.forEach((stat, index) => { - if (index < stats.length - 1) { - if (stat.date !== stats[index + 1].date) { - chartLabels.push(stat.date); - downloads.push(stat.downloads); - installs.push(stat.installs); - confirmations.push(stat.confirmations); - } - } - if (index === stats.length - 1) { - chartLabels.push(stat.date); - downloads.push(stat.downloads); - installs.push(stat.installs); - confirmations.push(stat.confirmations); - } - }); - - if (!this.state.downloadsToggle) { - downloads = []; - } - if (!this.state.installsToggle) { - installs = []; - } - if (!this.state.confirmationsToggle) { - confirmations = []; - } - - const chartData = { - datasets: [ - { - backgroundColor: "#FFFFFF00", - borderColor: "#FF6384", - data: downloads, - label: "Downloads" - }, - { - backgroundColor: "#FFFFFF00", - borderColor: "#36A2EB", - data: installs, - label: "Installs" - }, - { - backgroundColor: "#FFFFFF00", - borderColor: "#9966FF", - data: confirmations, - label: "Confirmations" - } - ], - labels: chartLabels - }; - - const chartSettings = { - data: chartData, - options: { - legend: { - display: false - }, - scales: { - yAxes: [ - { - display: false - } - ] - } - }, - type: "line" - }; - - const myChart = new Chart(node, chartSettings); - } - - public render() { - const confirmationsOpacity = this.state.confirmationsToggle ? 1 : 0.5; - const installsOpacity = this.state.installsToggle ? 1 : 0.5; - const downloadsOpacity = this.state.downloadsToggle ? 1 : 0.5; - return ( - -
- Referrals Stats -
-
- {this.props.referralCodes.length > 0 && ( - (this.node = node)} - /> - )} - {this.props.referralCodes.length === 0 && ( -
No referral codes
- )} -
-
- -
-
-
-
-
this.handleDataSelect(2)} - style={{ - backgroundColor: "#9966FF", - borderRadius: "50%", - cursor: "pointer", - height: "16px", - marginRight: "4px", - marginTop: "4px", - opacity: confirmationsOpacity, - width: "16px" - }} - /> -
- Confirmation -
-
-
this.handleDataSelect(1)} - style={{ display: "flex" }} - > -
-
- Installs -
-
-
this.handleDataSelect(0)} - style={{ display: "flex" }} - > -
-
- Downloads -
-
-
-
- - ); - } -} - -function ReferralCodeSelect(props) { - const dropdownOptions = props.referralCodes.map((referralCode, index) => ( - - )); - if (props.referralCodes.length > 0) { - return ( - - ); - } else { - return ; - } -} diff --git a/app/javascript/views/routes.ts b/app/javascript/views/routes.ts index 97b21d2b26..c64ccee749 100644 --- a/app/javascript/views/routes.ts +++ b/app/javascript/views/routes.ts @@ -1,5 +1,12 @@ /* tslint:disable:object-literal-sort-keys */ export default { + admin: { + promo_registrations: { + show: { + path: "/publishers/{publisher_id}/promo_registrations/for_referral_code?referral_code={referral_code}" + } + } + }, payments: { path: "/partners/payments", invoices: { @@ -18,8 +25,7 @@ export default { publishers: { promo_registrations: { show: { - path: - "{id}/promo_registrations/for_referral_code?referral_code={referral_code}" + path: "{id}/promo_registrations/for_referral_code?referral_code={referral_code}" } } } diff --git a/app/jobs/promo/register_channel_for_promo_job.rb b/app/jobs/promo/register_channel_for_promo_job.rb new file mode 100644 index 0000000000..0320bf7d1b --- /dev/null +++ b/app/jobs/promo/register_channel_for_promo_job.rb @@ -0,0 +1,15 @@ +# Registers a single channel for a promo immediately after verification +module Promo + class RegisterChannelForPromoJob < ApplicationJob + include PromosHelper + queue_as :default + + def perform(channel:) + if Promo::PublisherChannelsRegistrar.new(publisher: channel.publisher).perform + PromoMailer.new_channel_registered_2018q1(channel.publisher, channel).deliver_later + else + Rails.logger.warn("Failed to register newly verified channel #{channel} with promo server") + end + end + end +end diff --git a/app/jobs/promo/register_publisher_for_promo_job.rb b/app/jobs/promo/register_publisher_for_promo_job.rb new file mode 100644 index 0000000000..7545196dbd --- /dev/null +++ b/app/jobs/promo/register_publisher_for_promo_job.rb @@ -0,0 +1,19 @@ +# Used to register a publisher with > 5 channels async +module Promo + class RegisterPublisherForPromoJob < ApplicationJob + include PromosHelper + queue_as :default + + def perform(publisher:) + return unless publisher.promo_enabled_2018q1 + Rails.logger.info("Registering publisher #{publisher.id} for promo async.") + + if Promo::PublisherChannelsRegistrar.new(publisher: publisher).perform + promo_enabled_channels = publisher.channels.joins(:promo_registration) + PromoMailer.promo_activated_2018q1_verified(publisher, promo_enabled_channels).deliver + else + Rails.logger.warn("Failed to register publisher #{publisher.id} with promo server async.") + end + end + end +end diff --git a/app/jobs/promo/update_status.rb b/app/jobs/promo/update_status.rb new file mode 100644 index 0000000000..d7608d87db --- /dev/null +++ b/app/jobs/promo/update_status.rb @@ -0,0 +1,22 @@ +# Registers a single channel for a promo immediately after verification +module Promo + class UpdateStatus < ApplicationJob + queue_as :default + + def perform(id:, status:) + client = Promo::Client.new + + # Remove previous states, this prevents from any unique index constraints from being violated + states = client.owner_state.find(id: id) + states.each do |state| + client.owner_state.destroy(id: id, state: state) + end + + if status == PublisherStatusUpdate::SUSPENDED + client.owner_state.create(id: id, state: Promo::Models::OwnerState::State::SUSPEND) + elsif status == PublisherStatusUpdate::ONLY_USER_FUNDS + client.owner_state.create(id: id, state: Promo::Models::OwnerState::State::NO_UGP) + end + end + end +end diff --git a/app/jobs/register_channel_for_promo_job.rb b/app/jobs/register_channel_for_promo_job.rb deleted file mode 100644 index 9429929d8d..0000000000 --- a/app/jobs/register_channel_for_promo_job.rb +++ /dev/null @@ -1,13 +0,0 @@ -# Registers a single channel for a promo immediately after verification -class RegisterChannelForPromoJob < ApplicationJob - include PromosHelper - queue_as :default - - def perform(channel:) - if Promo::PublisherChannelsRegistrar.new(publisher: channel.publisher).perform - PromoMailer.new_channel_registered_2018q1(channel.publisher, channel).deliver_later - else - Rails.logger.warn("Failed to register newly verified channel #{channel} with promo server") - end - end -end diff --git a/app/jobs/register_publisher_for_promo_job.rb b/app/jobs/register_publisher_for_promo_job.rb deleted file mode 100644 index 2a21158f6f..0000000000 --- a/app/jobs/register_publisher_for_promo_job.rb +++ /dev/null @@ -1,17 +0,0 @@ -# Used to register a publisher with > 5 channels async -class RegisterPublisherForPromoJob < ApplicationJob - include PromosHelper - queue_as :default - - def perform(publisher:) - return unless publisher.promo_enabled_2018q1 - Rails.logger.info("Registering publisher #{publisher.id} for promo async.") - - if Promo::PublisherChannelsRegistrar.new(publisher: publisher).perform - promo_enabled_channels = publisher.channels.joins(:promo_registration) - PromoMailer.promo_activated_2018q1_verified(publisher, promo_enabled_channels).deliver - else - Rails.logger.warn("Failed to register publisher #{publisher.id} with promo server async.") - end - end -end diff --git a/app/models/channel.rb b/app/models/channel.rb index 6d55b4a491..3f515cf540 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -373,7 +373,7 @@ def clear_verified_at_if_necessary end def register_channel_for_promo - RegisterChannelForPromoJob.new.perform(channel: self) + Promo::RegisterChannelForPromoJob.new.perform(channel: self) end def notify_slack diff --git a/app/models/json_builders/channels_json_builder_v2.rb b/app/models/json_builders/channels_json_builder_v2.rb index 07f119b8d1..b0ffb987f1 100644 --- a/app/models/json_builders/channels_json_builder_v2.rb +++ b/app/models/json_builders/channels_json_builder_v2.rb @@ -57,7 +57,7 @@ def include_verified_channel(verified_channel) verified_channel.details.channel_identifier, true, false, - "00000000-0000-0000-0000-000000000000", + Rails.env.development? || Rails.env.staging? ? verified_channel.publisher.uphold_connection.address : "00000000-0000-0000-0000-000000000000", site_banner_details(verified_channel), ]) end diff --git a/app/models/publisher_status_update.rb b/app/models/publisher_status_update.rb index 99646cbfc0..c9dd7bfccb 100644 --- a/app/models/publisher_status_update.rb +++ b/app/models/publisher_status_update.rb @@ -11,6 +11,8 @@ class PublisherStatusUpdate < ApplicationRecord ALL_STATUSES = [CREATED, ONBOARDING, ACTIVE, SUSPENDED, LOCKED, NO_GRANTS, DELETED, HOLD, ONLY_USER_FUNDS].freeze + USER_SELECTABLE = [ACTIVE, SUSPENDED, NO_GRANTS, HOLD, ONLY_USER_FUNDS].freeze + DESCRIPTIONS = { CREATED => "User has signed up but not signed in.", ONBOARDING => "User has signed in but not completed entering their information", @@ -30,6 +32,20 @@ class PublisherStatusUpdate < ApplicationRecord validates :publisher_id, presence: true + # After a user creates a new status then we should check to see the previous staus and call backing server + after_create :update_services, if: :should_update? + + # Queues a job to call the promo server to update the owner state for the publisher based on the status + # + # @return [nil] + def update_services + Promo::UpdateStatus.perform_later(id: publisher_id, status: status) + end + + def should_update? + [ACTIVE, SUSPENDED, ONLY_USER_FUNDS].include?(status) + end + def to_s status end diff --git a/app/services/base_api_client.rb b/app/services/base_api_client.rb index 8260cf48da..41f6106116 100644 --- a/app/services/base_api_client.rb +++ b/app/services/base_api_client.rb @@ -1,4 +1,5 @@ class BaseApiClient < BaseService + private # Make a GET request. # # path - [String] the path relative to the endpoint @@ -47,13 +48,18 @@ def delete(path, options = {}) request(:delete, path, form: options) end - private - def api_base_uri raise "specify me" end + def perform_offline? + false + end + def request(method, path, payload = {}) + # Mock out the request + return Struct.new(:body).new("{}") if perform_offline? + @connection.send(method) do |req| req.url [api_base_uri, path].join('') req.headers["Authorization"] = api_authorization_header diff --git a/app/services/channels/contest_channel.rb b/app/services/channels/contest_channel.rb index 56a2193db8..c888aeddc0 100644 --- a/app/services/channels/contest_channel.rb +++ b/app/services/channels/contest_channel.rb @@ -14,7 +14,7 @@ def perform suspended: @channel.publisher.suspended? ) - raise SuspendedPublisherError if @channel.publisher.suspended? + raise SuspendedPublisherError if @channel.publisher.suspended? || @channel.publisher.only_user_funds? ActiveRecord::Base.transaction do @contested_by.verified = false diff --git a/app/services/payout_report_publisher_includer.rb b/app/services/payout_report_publisher_includer.rb index 0ac9e5464a..86e0537a62 100644 --- a/app/services/payout_report_publisher_includer.rb +++ b/app/services/payout_report_publisher_includer.rb @@ -6,7 +6,7 @@ def initialize(payout_report:, publisher:, should_send_notifications:) end def perform - return if !@publisher.has_verified_channel? || @publisher.locked? || @publisher.excluded_from_payout? || @publisher.no_grants? || @publisher.hold? + return if !@publisher.has_verified_channel? || @publisher.locked? || @publisher.excluded_from_payout? || @publisher.hold? publisher_has_unsettled_balance = false create_uphold_card_for_default_currency_if_needed diff --git a/app/services/promo/client.rb b/app/services/promo/client.rb index ac3a3df7bc..025b77a9ed 100644 --- a/app/services/promo/client.rb +++ b/app/services/promo/client.rb @@ -10,8 +10,8 @@ def owner_state private - def perform_offline - true + def perform_offline? + Rails.application.secrets[:api_promo_base_uri].blank? end def api_base_uri diff --git a/app/services/promo/models/owner_state.rb b/app/services/promo/models/owner_state.rb index 5b90c9fc0a..1641b99fc2 100644 --- a/app/services/promo/models/owner_state.rb +++ b/app/services/promo/models/owner_state.rb @@ -45,7 +45,7 @@ def create(id:, state:) # Removes the state for the specified owner # # @param [String] id The publisher id - # @state [Promo::Models::OwnerState::State] state The state to remove from the owner. + # @param [Promo::Models::OwnerState::State] state The state to remove from the owner. # # @return [true] if destroy was a success def destroy(id:, state:) diff --git a/app/views/admin/publishers/publisher_status_updates/index.html.slim b/app/views/admin/publishers/publisher_status_updates/index.html.slim index 063451178d..2856d6a8c7 100644 --- a/app/views/admin/publishers/publisher_status_updates/index.html.slim +++ b/app/views/admin/publishers/publisher_status_updates/index.html.slim @@ -2,52 +2,60 @@ h5.admin-header Change current status for #{@publisher.name} -- if @publisher.suspended? - .my-3 - p.alert.alert-warning - = "If you are planning on unsuspending this user please provide evidence and justification for your actions. " - br - br - = "If you believe this user is a potential outlier please contact the appropriate group and alert them." -- else - .my-3 - p.alert.alert-warning - = "If you are planning on suspending this user please provide evidence and justification for your actions. This may come in the form of excel, git, or a reasonable explaination of the users misdeeds." - br - br - = "Please view more information about the publisher guidelines here." - br - = link_to "https://community.brave.com/t/a-note-to-publishers/48733", "https://community.brave.com/t/a-note-to-publishers/48733" -= form_for(@publisher, url: admin_publisher_publisher_status_updates_path(@publisher.id), method: :create) do |f| - .form-group - = label_tag(:status_label, "Status") - = select_tag(:publisher_status, options_for_select(PublisherStatusUpdate::ALL_STATUSES, @publisher.last_status_update), class: "form-control") +- if @publisher.only_user_funds? + p.alert.alert-warning + = "This user has been placed into an #{@publisher.last_status_update} status. Once a user has been placed into this status it is not possible to remove." + br + br + = "If you believe this was a mistake please contact the appropriate product owners and engage the engineering team." - .form-group - = label_tag(:note_label, "Include a note: ") - = text_area_tag(:note, '', class: 'form-control', rows: 5, required: true) - .form-group - label - = check_box_tag("send_email", true) - = " Send email (suspended & hold only)" - = f.submit("Update Status", class: 'btn btn-primary') +- else + - if @publisher.suspended? + .my-3 + p.alert.alert-warning + = "If you are planning on unsuspending this user please provide evidence and justification for your actions. " + br + br + = "If you believe this user is a potential outlier please contact the appropriate group and alert them." + - else + .my-3 + p.alert.alert-warning + = "If you are planning on suspending this user please provide evidence and justification for your actions. This may come in the form of excel, git, or a reasonable explaination of the users misdeeds." + br + br + = "Please view more information about the publisher guidelines here." + br + = link_to "https://community.brave.com/t/a-note-to-publishers/48733", "https://community.brave.com/t/a-note-to-publishers/48733" + = form_for(@publisher, url: admin_publisher_publisher_status_updates_path(@publisher.id), method: :create) do |f| + .form-group + = label_tag(:status_label, "Status") + = select_tag(:publisher_status, options_for_select(PublisherStatusUpdate::USER_SELECTABLE, @publisher.last_status_update), class: "form-control") + .form-group + = label_tag(:note_label, "Include a note: ") + = text_area_tag(:note, '', class: 'form-control', rows: 5, required: true) + .form-group + label + = check_box_tag("send_email", true) + = " Send email (suspended & hold only)" + = f.submit("Update Status", class: 'btn btn-primary') -.legend.my-5 - h2.mb-0 Legend - table.table - thead - tr - th Status - th Description - tbody - - PublisherStatusUpdate::DESCRIPTIONS.each do |description| + + .legend.my-5 + h2.mb-0 Legend + table.table + thead tr - td - span class=status_badge_class(description.first) - = description.first - td= description.second + th Status + th Description + tbody + - PublisherStatusUpdate::DESCRIPTIONS.each do |description| + tr + td + span class=status_badge_class(description.first) + = description.first + td= description.second hr diff --git a/app/views/admin/publishers/referrals/index.html.slim b/app/views/admin/publishers/referrals/index.html.slim new file mode 100644 index 0000000000..6de1075c39 --- /dev/null +++ b/app/views/admin/publishers/referrals/index.html.slim @@ -0,0 +1,9 @@ +- if @publisher.promo_registrations.present? + = hidden_field_tag 'referrals-hidden-tags', @publisher.promo_registrations.has_stats.pluck(:referral_code).to_json + = hidden_field_tag 'publisher_id', @publisher.id +.row + .col.bot-marg + .dashboard-panel--wrapper + #channel-referrals-stats-chart + canvas id="channel-referrals-stats-chart-canvas" += javascript_pack_tag 'views/admin/referrals/Referrals' diff --git a/app/views/admin/publishers/show.html.slim b/app/views/admin/publishers/show.html.slim index b0feb777f5..db0b44d123 100644 --- a/app/views/admin/publishers/show.html.slim +++ b/app/views/admin/publishers/show.html.slim @@ -74,6 +74,15 @@ hr .db-info-row .db-field = "Referral confirmations:" .db-value = publisher_referral_totals(@publisher)[PromoRegistration::FINALIZED] + .db-info-row + .db-field + = "Referral owner status" + span data-tooltip="If the user is 'suspended' or 'no-ugp' this means their promos will redirect to the homepage" + = fa_icon 'question-circle', class: 'mx-1' + = ": " + + .db-value= @referral_owner_status + #statement-section.split-row h3.admin-header = "Statements" .statement diff --git a/app/views/admin/referrals/index.html.slim b/app/views/admin/referrals/index.html.slim deleted file mode 100644 index eabf47a5e6..0000000000 --- a/app/views/admin/referrals/index.html.slim +++ /dev/null @@ -1 +0,0 @@ -= javascript_pack_tag 'views/referrals/Referrals' \ No newline at end of file diff --git a/app/views/admin/referrals/show.html.erb b/app/views/admin/referrals/show.html.erb deleted file mode 100644 index ad772796b3..0000000000 --- a/app/views/admin/referrals/show.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -<%= content_tag :div, - id: "referrals_data", - data: @data.to_json do %> -<%= javascript_pack_tag 'views/admin/referrals/Referrals' %> -<% end %> \ No newline at end of file diff --git a/app/views/admin/referrals/show.html.slim b/app/views/admin/referrals/show.html.slim deleted file mode 100644 index 52f1aab114..0000000000 --- a/app/views/admin/referrals/show.html.slim +++ /dev/null @@ -1 +0,0 @@ -= javascript_pack_tag 'views/admin/referrals/Referrals' \ No newline at end of file diff --git a/app/views/publishers/_channel.html.slim b/app/views/publishers/_channel.html.slim new file mode 100644 index 0000000000..85388b261f --- /dev/null +++ b/app/views/publishers/_channel.html.slim @@ -0,0 +1,90 @@ +.row.channel-row id=("channel_row_#{channel.id}") data-remove-message=(t("shared.channel_removed")) + .col.mb-4 + div class=("channel-panel channel-#{channel_verification_status(channel)}") + .channel-panel--intro + .channel-panel--intro-icon + img src=(channel_type_icon_url(channel)) width=16 height=16 + .channel-panel--intro-body + = channel_type(channel).upcase + - if channel.verified? + - if current_publisher.promo_status(promo_running?) == :active && channel.promo_enabled? && !current_publisher.only_user_funds? + .channel--promo-info-container + = link_to("", tweet_url(channel.promo_registration.referral_code), target: :_blank, class: "promo-share-button promo-share-button-twitter") + = link_to("", facebook_url(channel.promo_registration.referral_code), target: :_blank, class: "promo-share-button promo-share-button-facebook") + .referral-link-url.promo-info-item + span= https_referral_url(channel.promo_registration.referral_code) + .referral-link-button.referral-link-button-desktop.promo-info-item + span= t("promo.shared.referral_link") + .referral-link-button.referral-link-button-mobile.promo-info-item.copy-button data-clipboard-text="#{https_referral_url(channel.promo_registration.referral_code)}" + span= t("promo.shared.referral_link") + .referral-link-copy-button.promo-info-item.copy-button data-clipboard-text="#{https_referral_url(channel.promo_registration.referral_code)}" + span= t("promo.shared.copy") + / Need to tell the users that it will take 48 hours for their verified channel to appear in the web browser + - if channel.created_at > 2.days.ago + .channel-info + .info-popup + .info--details + // JS will break if the next two elements aren't siblings + span.info--what-happened=t(".verification_status_title") + span.info--explanation + span.verification-failed-explanation--content + = t(".verification_status") + - else + .channel-status.float-right + .bat-channel + h4.bat-channel--amount id=("channel_amount_bat_#{channel.details.channel_identifier}") + = publisher_channel_bat_balance(current_publisher, channel.details.channel_identifier) + span.bat-channel--currency= " BAT" + .bat-channel--period + = t(".channel_balance_period") + .channel-summary + h3= channel.publication_title + .channel-details + .added-date + = t(".added", date: channel.created_at.to_date.iso8601) + span.separator + = ' | ' + a.remove-channel href="#" data-channel-id=(channel.id) + = t(".remove_verified") + script type="text/html" data-js-channel-removal-confirmation-template=(channel.id) + = render "publishers/remove_channel_modal", channel: channel + = form_for(channel, html: {id: "remove_channel_#{channel.id}"}) do |f| + - elsif channel.verification_failed? + .channel-status.float-right + .verification-failed + .verification-failed--header + = t("helpers.channels.verification_failure") + .verification-failed--details + // JS will break if the next two elements aren't siblings + span.verification-failed--what-happened=t("helpers.channels.verification_failure_what_happened") + span.verification-failed--explanation + span.verification-failed-explanation--content + = channel_verification_details(channel).upcase_first + span.separator + = ' | ' + a.remove-channel href="#" data-channel-id=(channel.id) + = t(".remove_verified") + script type="text/html" data-js-channel-removal-confirmation-template=(channel.id) + = render "publishers/remove_channel_modal", channel: channel + = form_for(channel, html: {id: "remove_channel_#{channel.id}"}) do |f| + = link_to(t(".try_again"), channel_next_step_path(channel), class: "btn btn-primary try-again") + - elsif channel.verification_pending? + .channel-status.float-right + = t("shared.channel_contested", time_until_transfer: time_until_transfer(channel)) + - elsif channel.verification_awaiting_admin_approval? + .channel-status.float-right + = t("helpers.channels.verification_awaiting_admin_approval") + - else + .channel-status.float-right + = link_to(t(".lets_finish"), channel_next_step_path(channel), class: "btn btn-primary") + .channel-progress.float-right + .one-more-step= t(".one_more_step") + - unless channel.verified + .channel-summary + h3.unverified = channel.publication_title + - if channel.contested_by_channel + .channel-contested + p = t "shared.channel_contested_by", time_until_transfer: time_until_transfer(channel.contested_by_channel), + verified_by_email: channel.contested_by_channel.publisher.email + a.reject_transfer href=token_reject_transfer_url(channel, channel.contest_token) + = t ".reject_transfer" diff --git a/app/views/publishers/_promo_panel.html.slim b/app/views/publishers/_promo_panel.html.slim new file mode 100644 index 0000000000..48bfd879e7 --- /dev/null +++ b/app/views/publishers/_promo_panel.html.slim @@ -0,0 +1,45 @@ +.col-md.mb-4 + .promo-panel + - if current_publisher.promo_status(promo_running?) == :inactive + .promo-panel-item.promo-panel-item-info + #promo-limited-time= t("promo.activate.sub_header_one") + h3#promo-header= t("promo.activate.header") + #promo-referring-fans= t("promo.activate.sub_header_two") + .promo-panel-item.promo-panel-item-button + = form_for current_publisher, { method: :get, url: promo_registrations_path } do |f| + = f.submit(t("promo.activate.button-dashboard").upcase, class: "btn btn-primary", id: "activate-promo-dashboard-button" ) + - elsif current_publisher.only_user_funds? + + .promo-panel-item.promo-panel-item-alert + #promo-panel-alert + == t("promo.dashboard.violation") + + - elsif current_publisher.has_verified_channel? + - publisher_referral_totals = publisher_referral_totals(current_publisher) + .promo-flex-col + .promo-flex-row.promo-flex-row-main + .promo-flex-col.promo-panel-title + .promo-panel-title-item + =t("promo.dashboard.referral") + .promo-flex-row.promo-flex-row-sub + =t("promo.dashboard.check_statement") + .promo-flex-stats + .promo-flex-col.promo-flex-col-downloaded + .promo-panel-header title=t("promo.dashboard.tooltip.downloaded") + =t("promo.dashboard.downloaded").upcase + .promo-panel-number + = publisher_referral_totals[PromoRegistration::RETRIEVALS] + .promo-flex-col.promo-flex-col-installed + .promo-panel-header title=t("promo.dashboard.tooltip.installed") + =t("promo.dashboard.installed").upcase + .promo-panel-number + = publisher_referral_totals[PromoRegistration::FIRST_RUNS] + .promo-flex-col.promo-flex-col-confirmed + .promo-panel-header title=t("promo.dashboard.tooltip.confirmed") + =t("promo.dashboard.confirmed").upcase + .promo-panel-number + = publisher_referral_totals[PromoRegistration::FINALIZED] + - else + .promo-panel-item.promo-panel-item-alert + #promo-panel-alert + = t("promo.dashboard.add_channel") diff --git a/app/views/publishers/home.html.slim b/app/views/publishers/home.html.slim index 25cdae9474..25ecd4f4a7 100644 --- a/app/views/publishers/home.html.slim +++ b/app/views/publishers/home.html.slim @@ -37,7 +37,7 @@ script type="text/html" id="confirm_default_currency_modal_wrapper" = image_tag("bat-logo@2x.png", class: "", width: 60, height: 60) .amounts h4.balance-header - span = t ".balance_pending" + span = t(".balance_pending") .bat span.bat-amount#bat_amount = publisher_overall_bat_balance(current_publisher) span.bat-code= " BAT" @@ -46,51 +46,14 @@ script type="text/html" id="confirm_default_currency_modal_wrapper" - if payout_in_progress? .payout_in_progress span - i = t ".payout_in_progress" + i = t(".payout_in_progress") - else .next_deposit_date - span = t ".next_deposit_date" + span = t(".next_deposit_date") span = next_deposit_date + - if promo_running? - .col-md.mb-4 - .promo-panel - - if current_publisher.promo_status(promo_running?) == :inactive - .promo-panel-item.promo-panel-item-info - #promo-limited-time= t("promo.activate.sub_header_one") - h3#promo-header= t("promo.activate.header") - #promo-referring-fans= t("promo.activate.sub_header_two") - .promo-panel-item.promo-panel-item-button - = form_for current_publisher, { method: :get, url: promo_registrations_path } do |f| - = f.submit(t("promo.activate.button-dashboard").upcase, class: "btn btn-primary", id: "activate-promo-dashboard-button" ) - - elsif current_publisher.has_verified_channel? - - publisher_referral_totals = publisher_referral_totals(current_publisher) - .promo-flex-col - .promo-flex-row.promo-flex-row-main - .promo-flex-col.promo-panel-title - .promo-panel-title-item - =t("promo.dashboard.referral") - .promo-flex-row.promo-flex-row-sub - =t("promo.dashboard.check_statement") - .promo-flex-stats - .promo-flex-col.promo-flex-col-downloaded - .promo-panel-header title=t("promo.dashboard.tooltip.downloaded") - =t("promo.dashboard.downloaded").upcase - .promo-panel-number - = publisher_referral_totals[PromoRegistration::RETRIEVALS] - .promo-flex-col.promo-flex-col-installed - .promo-panel-header title=t("promo.dashboard.tooltip.installed") - =t("promo.dashboard.installed").upcase - .promo-panel-number - = publisher_referral_totals[PromoRegistration::FIRST_RUNS] - .promo-flex-col.promo-flex-col-confirmed - .promo-panel-header title=t("promo.dashboard.tooltip.confirmed") - =t("promo.dashboard.confirmed").upcase - .promo-panel-number - = publisher_referral_totals[PromoRegistration::FINALIZED] - - else - .promo-panel-item.promo-panel-item-alert - #promo-panel-alert - = t("promo.dashboard.add_channel") + = render partial: 'promo_panel' - elsif current_publisher.channels.empty? .col-md.mb-4 .add-channel-cta @@ -190,100 +153,11 @@ script type="text/html" id="confirm_default_currency_modal_wrapper" / Don't show if none of the promo registrations has a valid stat - - if current_publisher.promo_registrations.present? && current_publisher.promo_registrations.has_stats.select { |promo_registration| promo_registration.stats_by_date.present? }.present? + - if !current_publisher.only_user_funds? && current_publisher.promo_registrations.present? && current_publisher.promo_registrations.has_stats.select { |promo_registration| promo_registration.stats_by_date.present? }.present? = render "publishers/referral_charts", current_publisher: current_publisher - current_publisher.channels.visible.each do |channel| - .row.channel-row id=("channel_row_#{channel.id}") data-remove-message=(t("shared.channel_removed")) - .col.mb-4 - div class=("channel-panel channel-#{channel_verification_status(channel)}") - .channel-panel--intro - .channel-panel--intro-icon - img src=(channel_type_icon_url(channel)) width=16 height=16 - .channel-panel--intro-body - = channel_type(channel).upcase - - if channel.verified? - - if current_publisher.promo_status(promo_running?) == :active && channel.promo_enabled? && !current_publisher.only_user_funds? - .channel--promo-info-container - = link_to("", tweet_url(channel.promo_registration.referral_code), target: :_blank, class: "promo-share-button promo-share-button-twitter") - = link_to("", facebook_url(channel.promo_registration.referral_code), target: :_blank, class: "promo-share-button promo-share-button-facebook") - .referral-link-url.promo-info-item - span= https_referral_url(channel.promo_registration.referral_code) - .referral-link-button.referral-link-button-desktop.promo-info-item - span= t("promo.shared.referral_link") - .referral-link-button.referral-link-button-mobile.promo-info-item.copy-button data-clipboard-text="#{https_referral_url(channel.promo_registration.referral_code)}" - span= t("promo.shared.referral_link") - .referral-link-copy-button.promo-info-item.copy-button data-clipboard-text="#{https_referral_url(channel.promo_registration.referral_code)}" - span= t("promo.shared.copy") - / Need to tell the users that it will take 48 hours for their verified channel to appear in the web browser - - if channel.created_at > 2.days.ago - .channel-info - .info-popup - .info--details - // JS will break if the next two elements aren't siblings - span.info--what-happened=t(".channel.verification_status_title") - span.info--explanation - span.verification-failed-explanation--content - = t(".channel.verification_status") - - else - .channel-status.float-right - .bat-channel - h4.bat-channel--amount id=("channel_amount_bat_#{channel.details.channel_identifier}") - = publisher_channel_bat_balance(current_publisher, channel.details.channel_identifier) - span.bat-channel--currency= " BAT" - .bat-channel--period - = t(".channel_balance_period") - .channel-summary - h3= channel.publication_title - .channel-details - .added-date - = t ".channel.added", date: channel.created_at.to_date.iso8601 - span.separator - = ' | ' - a.remove-channel href="#" data-channel-id=(channel.id) - = t ".channel.remove_verified" - script type="text/html" data-js-channel-removal-confirmation-template=(channel.id) - = render "publishers/remove_channel_modal", channel: channel - = form_for(channel, html: {id: "remove_channel_#{channel.id}"}) do |f| - - elsif channel.verification_failed? - .channel-status.float-right - .verification-failed - .verification-failed--header - = t("helpers.channels.verification_failure") - .verification-failed--details - // JS will break if the next two elements aren't siblings - span.verification-failed--what-happened=t("helpers.channels.verification_failure_what_happened") - span.verification-failed--explanation - span.verification-failed-explanation--content - = channel_verification_details(channel).upcase_first - span.separator - = ' | ' - a.remove-channel href="#" data-channel-id=(channel.id) - = t ".channel.remove_verified" - script type="text/html" data-js-channel-removal-confirmation-template=(channel.id) - = render "publishers/remove_channel_modal", channel: channel - = form_for(channel, html: {id: "remove_channel_#{channel.id}"}) do |f| - = link_to(t(".channel.try_again"), channel_next_step_path(channel), class: "btn btn-primary try-again") - - elsif channel.verification_pending? - .channel-status.float-right - = t("shared.channel_contested", time_until_transfer: time_until_transfer(channel)) - - elsif channel.verification_awaiting_admin_approval? - .channel-status.float-right - = t("helpers.channels.verification_awaiting_admin_approval") - - else - .channel-status.float-right - = link_to(t(".channel.lets_finish"), channel_next_step_path(channel), class: "btn btn-primary") - .channel-progress.float-right - .one-more-step= t ".channel.one_more_step" - - unless channel.verified - .channel-summary - h3.unverified = channel.publication_title - - if channel.contested_by_channel - .channel-contested - p = t "shared.channel_contested_by", time_until_transfer: time_until_transfer(channel.contested_by_channel), - verified_by_email: channel.contested_by_channel.publisher.email - a.reject_transfer href=token_reject_transfer_url(channel, channel.contest_token) - = t ".channel.reject_transfer" + = render partial: 'channel', locals: { channel: channel } .row id="add_channel_placeholder" .col.mb-4 diff --git a/config/environments/development.rb b/config/environments/development.rb index 7883359195..89bd9fbe70 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -68,7 +68,7 @@ config.assets.quiet = true # Raises error for missing translations - # config.action_view.raise_on_missing_translations = true + config.action_view.raise_on_missing_translations = true # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. diff --git a/config/locales/en.yml b/config/locales/en.yml index 03247cf504..3ce4fda90d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -534,14 +534,14 @@ en: heading: Contact edit: Edit Contact update: Update - channel: - added: added %{date} - remove_verified: Remove Channel - one_more_step: One more step to complete... - lets_finish: Let's Finish - try_again: Try Again - verification_status: Verification status will be displayed in the Brave browser within the next 24-48 hours - verification_status_title: Verification Information + channel: + added: added %{date} + remove_verified: Remove Channel + one_more_step: One more step to complete... + lets_finish: Let's Finish + try_again: Try Again + verification_status: Verification status will be displayed in the Brave browser within the next 24-48 hours + verification_status_title: Verification Information channel_balance_period: CURRENT PERIOD omniauth_callbacks: register_youtube_channel: @@ -684,6 +684,7 @@ en: confirmed: "confirmed" active_referral: "active" add_channel: "Add a web site or youtube channel below to get the promo link!" + violation: We regret to inform you that we've suspended your ability to participate in Brave Rewards Program due to violation of our Terms of Service. However, you can still earn and redeem rewards from participating in our other programs. referral: "Referral Promo Stats" check_statement: "Check your statement for this period's payout." tooltip: diff --git a/config/routes.rb b/config/routes.rb index afb2b5a34b..6c675fc0f3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -163,7 +163,6 @@ get :duplicates end end - resources :faq_categories, except: [:show] resources :faqs, except: [:show] resources :payout_reports, only: %i(index show create) do @@ -184,11 +183,11 @@ get :cancel_two_factor_authentication_removal end resources :publisher_notes - resources :reports resources :publisher_status_updates, controller: 'publishers/publisher_status_updates' + resources :referrals, controller: 'publishers/referrals' + resources :reports end resources :channel_transfers - resources :referrals resources :payments resources :channel_approvals resources :security diff --git a/test/controllers/admin/publishers/publisher_status_updates_controller_test.rb b/test/controllers/admin/publishers/publisher_status_updates_controller_test.rb index 58e624fc7b..17b34fee86 100644 --- a/test/controllers/admin/publishers/publisher_status_updates_controller_test.rb +++ b/test/controllers/admin/publishers/publisher_status_updates_controller_test.rb @@ -50,7 +50,7 @@ class Admin::Publishers::PublisherStatusUpdatesControllerTest < ActionDispatch:: sign_in admin publisher = publishers(:uphold_connected) - assert_enqueued_jobs(1) do + assert_enqueued_jobs(2) do post( admin_publisher_publisher_status_updates_path( publisher_id: publisher.id, @@ -67,7 +67,7 @@ class Admin::Publishers::PublisherStatusUpdatesControllerTest < ActionDispatch:: admin = publishers(:admin) sign_in admin publisher = publishers(:uphold_connected) - assert_enqueued_jobs(0) do + assert_enqueued_jobs(1) do post( admin_publisher_publisher_status_updates_path( publisher_id: publisher.id, diff --git a/test/features/site_channel_verification_test.rb b/test/features/site_channel_verification_test.rb index fa6189b2fc..4008aa97c6 100644 --- a/test/features/site_channel_verification_test.rb +++ b/test/features/site_channel_verification_test.rb @@ -151,6 +151,6 @@ def stub_verification_public_file(channel, body: nil, status: 200) visit verification_public_file_site_channel_path(channel) click_link(I18n.t("site_channels.shared.finish_verification_later")) - assert_content (I18n.t("publishers.home.channel.one_more_step")) + assert_content (I18n.t("publishers.channel.one_more_step")) end end diff --git a/test/helpers/publishers_helper_test.rb b/test/helpers/publishers_helper_test.rb index 644c9d8ae9..f55cec8b2a 100644 --- a/test/helpers/publishers_helper_test.rb +++ b/test/helpers/publishers_helper_test.rb @@ -45,6 +45,10 @@ def become_subclass def partner? false end + + def only_user_funds? + false + end end test "publisher_converted_overall_balance should return `CURRENCY unavailable` when no wallet is set" do diff --git a/yarn.lock b/yarn.lock index f13ffec108..d9c2b7772f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10758,3 +10758,4 @@ yargs@^7.0.0: which-module "^1.0.0" y18n "^3.2.1" yargs-parser "^5.0.0" +