From d53be2936110bc12aaa7e120a07bc71aab7fcb65 Mon Sep 17 00:00:00 2001 From: zenparsing Date: Thu, 22 Sep 2022 17:12:01 -0400 Subject: [PATCH] Add country selection to Rewards onboarding --- .../android/brave_rewards_native_worker.cc | 2 +- browser/extensions/api/brave_rewards_api.cc | 69 ++++-- browser/extensions/api/brave_rewards_api.h | 21 +- .../brave_rewards_action_view.cc | 21 +- .../webui/brave_rewards/rewards_panel_ui.cc | 21 ++ browser/ui/webui/brave_rewards_page_ui.cc | 13 +- browser/ui/webui/brave_webui_source.cc | 3 + common/extensions/api/brave_rewards.json | 45 ++-- .../brave_new_tab_ui/api/initialData.ts | 5 + .../components/default/rewards/index.tsx | 17 +- .../containers/newTab/index.tsx | 2 - .../newTab/settings/sponsoredImagesToggle.tsx | 4 +- .../reducers/rewards_reducer.ts | 1 + .../storage/new_tab_storage.ts | 1 + .../brave_rewards/browser/rewards_service.cc | 1 + .../brave_rewards/browser/rewards_service.h | 16 +- .../browser/rewards_service_impl.cc | 118 ++++++++--- .../browser/rewards_service_impl.h | 8 +- .../brave_rewards/browser/static_values.h | 4 +- .../common/rewards_browsertest_response.cc | 12 +- .../test/common/rewards_browsertest_util.cc | 10 +- .../browser/test/rewards_browsertest.cc | 77 ++++++- .../browser/test/rewards_p3a_browsertest.cc | 6 +- components/brave_rewards/common/pref_names.cc | 1 + components/brave_rewards/common/pref_names.h | 1 + .../_locales/en_US/messages.json | 97 +++++++++ .../reducers/rewards_panel_reducer.ts | 2 +- .../components/panel_overlays.tsx | 42 +++- .../lib/extension_api_adapter.ts | 31 ++- .../rewards_panel/lib/extension_host.ts | 18 +- .../rewards_panel/lib/initial_state.ts | 4 +- .../resources/rewards_panel/lib/interfaces.ts | 7 +- .../resources/rewards_panel/stories/index.tsx | 16 +- .../shared/components/icons/geo_pin_icon.tsx | 13 ++ .../components/newtab/rewards_card.style.ts | 4 + .../shared/components/newtab/rewards_card.tsx | 18 ++ .../newtab/select_country_card.style.ts | 76 +++++++ .../components/newtab/select_country_card.tsx | 48 +++++ .../components/newtab/stories/index.tsx | 2 + .../newtab/stories/locale_strings.ts | 3 + .../onboarding/assets/select_caret.svg | 3 + .../components/onboarding/country_select.tsx | 55 +++++ .../components/onboarding/css_mixins.ts | 24 +++ .../onboarding/icons/error_icon.tsx | 13 ++ .../onboarding/icons/success_icon.tsx | 13 ++ .../shared/components/onboarding/index.ts | 2 +- .../onboarding/main_button.style.ts | 5 + .../components/onboarding/main_button.tsx | 7 +- .../onboarding/rewards_opt_in_modal.style.ts | 117 ++++++++--- .../onboarding/rewards_opt_in_modal.tsx | 198 +++++++++++++++++- .../onboarding/rewards_tour_modal.style.ts | 2 +- .../components/onboarding/stories/index.tsx | 5 +- .../onboarding/stories/locale_strings.ts | 15 ++ .../resources/shared/lib/rewards_urls.ts | 1 + components/definitions/chromel.d.ts | 6 +- components/definitions/newTab.d.ts | 1 + components/resources/rewards_strings.grdp | 54 +++++ .../services/bat_ledger/bat_ledger_impl.cc | 5 +- .../services/bat_ledger/bat_ledger_impl.h | 3 +- .../public/interfaces/bat_ledger.mojom | 5 +- ios/browser/api/ledger/brave_ledger.mm | 87 ++++---- .../include/bat/ledger/ledger.h | 6 +- .../bat/ledger/public/interfaces/ledger.mojom | 8 +- .../src/bat/ledger/internal/ledger_impl.cc | 23 +- .../src/bat/ledger/internal/ledger_impl.h | 3 +- .../ledger/internal/promotion/promotion.cc | 18 +- .../bat/ledger/internal/promotion/promotion.h | 4 - .../src/bat/ledger/internal/wallet/wallet.cc | 33 +-- .../src/bat/ledger/internal/wallet/wallet.h | 14 +- .../ledger/internal/wallet/wallet_create.cc | 41 +++- .../ledger/internal/wallet/wallet_create.h | 5 +- .../ledger/internal/wallet/wallet_unittest.cc | 22 +- 72 files changed, 1348 insertions(+), 310 deletions(-) create mode 100644 components/brave_rewards/resources/shared/components/icons/geo_pin_icon.tsx create mode 100644 components/brave_rewards/resources/shared/components/newtab/select_country_card.style.ts create mode 100644 components/brave_rewards/resources/shared/components/newtab/select_country_card.tsx create mode 100644 components/brave_rewards/resources/shared/components/onboarding/assets/select_caret.svg create mode 100644 components/brave_rewards/resources/shared/components/onboarding/country_select.tsx create mode 100644 components/brave_rewards/resources/shared/components/onboarding/css_mixins.ts create mode 100644 components/brave_rewards/resources/shared/components/onboarding/icons/error_icon.tsx create mode 100644 components/brave_rewards/resources/shared/components/onboarding/icons/success_icon.tsx diff --git a/browser/brave_rewards/android/brave_rewards_native_worker.cc b/browser/brave_rewards/android/brave_rewards_native_worker.cc index 531b89e1acd4..01c4f86f7ed0 100644 --- a/browser/brave_rewards/android/brave_rewards_native_worker.cc +++ b/browser/brave_rewards/android/brave_rewards_native_worker.cc @@ -71,7 +71,7 @@ void BraveRewardsNativeWorker::Destroy(JNIEnv* env) { void BraveRewardsNativeWorker::CreateRewardsWallet(JNIEnv* env) { if (brave_rewards_service_) { - brave_rewards_service_->CreateRewardsWallet(base::DoNothing()); + brave_rewards_service_->CreateRewardsWallet("", base::DoNothing()); } } diff --git a/browser/extensions/api/brave_rewards_api.cc b/browser/extensions/api/brave_rewards_api.cc index f085a8143e7c..facb0f6ca074 100644 --- a/browser/extensions/api/brave_rewards_api.cc +++ b/browser/extensions/api/brave_rewards_api.cc @@ -81,6 +81,19 @@ RewardsPanelCoordinator* GetPanelCoordinator(ExtensionFunction* function) { return browser ? RewardsPanelCoordinator::FromBrowser(browser) : nullptr; } +std::string StringifyResult(ledger::mojom::CreateRewardsWalletResult result) { + switch (result) { + case ledger::mojom::CreateRewardsWalletResult::kSuccess: + return "success"; + case ledger::mojom::CreateRewardsWalletResult::kWalletGenerationDisabled: + return "wallet-generation-disabled"; + case ledger::mojom::CreateRewardsWalletResult::kGeoCountryAlreadyDeclared: + return "country-already-declared"; + case ledger::mojom::CreateRewardsWalletResult::kUnexpected: + return "unexpected-error"; + } +} + } // namespace namespace extensions { @@ -587,47 +600,65 @@ BraveRewardsCreateRewardsWalletFunction:: ExtensionFunction::ResponseAction BraveRewardsCreateRewardsWalletFunction::Run() { + auto params = brave_rewards::CreateRewardsWallet::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(params); + auto* profile = Profile::FromBrowserContext(browser_context()); auto* rewards_service = RewardsServiceFactory::GetForProfile(profile); if (!rewards_service) { return RespondNow(Error("RewardsService not available")); } - rewards_service->CreateRewardsWallet(base::BindOnce( - &BraveRewardsCreateRewardsWalletFunction::CreateRewardsWalletCallback, - this)); + rewards_service->CreateRewardsWallet( + params->country, + base::BindOnce( + &BraveRewardsCreateRewardsWalletFunction::CreateRewardsWalletCallback, + this)); return RespondLater(); } void BraveRewardsCreateRewardsWalletFunction::CreateRewardsWalletCallback( - ledger::mojom::Result result) { - Respond(OneArgument(base::Value(static_cast(result)))); + ledger::mojom::CreateRewardsWalletResult result) { + Respond(OneArgument(base::Value(StringifyResult(result)))); } -BraveRewardsGetRewardsWalletFunction::~BraveRewardsGetRewardsWalletFunction() = - default; +BraveRewardsGetAvailableCountriesFunction:: + ~BraveRewardsGetAvailableCountriesFunction() = default; -ExtensionFunction::ResponseAction BraveRewardsGetRewardsWalletFunction::Run() { - Profile* profile = Profile::FromBrowserContext(browser_context()); +ExtensionFunction::ResponseAction +BraveRewardsGetAvailableCountriesFunction::Run() { + auto* profile = Profile::FromBrowserContext(browser_context()); auto* rewards_service = RewardsServiceFactory::GetForProfile(profile); + if (!rewards_service) { - return RespondNow(Error("RewardsService not available")); + return RespondNow(Error("Rewards service is not initialized")); } - rewards_service->GetRewardsWallet(base::BindOnce( - &BraveRewardsGetRewardsWalletFunction::GetRewardsWalletCallback, this)); + rewards_service->GetAvailableCountries(base::BindOnce( + &BraveRewardsGetAvailableCountriesFunction::GetAvailableCountriesCallback, + this)); + return RespondLater(); } -void BraveRewardsGetRewardsWalletFunction::GetRewardsWalletCallback( - ledger::mojom::RewardsWalletPtr rewards_wallet) { - if (!rewards_wallet) { - return Respond(NoArguments()); +void BraveRewardsGetAvailableCountriesFunction::GetAvailableCountriesCallback( + std::vector countries) { + base::Value::List country_list; + for (auto& country : countries) { + country_list.Append(std::move(country)); } - base::Value::Dict dict; - dict.Set("paymentId", rewards_wallet->payment_id); - Respond(OneArgument(base::Value(std::move(dict)))); + Respond(OneArgument(base::Value(std::move(country_list)))); +} + +BraveRewardsGetDeclaredCountryFunction:: + ~BraveRewardsGetDeclaredCountryFunction() = default; + +ExtensionFunction::ResponseAction +BraveRewardsGetDeclaredCountryFunction::Run() { + auto* prefs = Profile::FromBrowserContext(browser_context())->GetPrefs(); + std::string country = prefs->GetString(::brave_rewards::prefs::kDeclaredGeo); + return RespondNow(OneArgument(base::Value(std::move(country)))); } BraveRewardsGetBalanceReportFunction::~BraveRewardsGetBalanceReportFunction() = diff --git a/browser/extensions/api/brave_rewards_api.h b/browser/extensions/api/brave_rewards_api.h index 1e33a0370ba8..d8a1e6f3c458 100644 --- a/browser/extensions/api/brave_rewards_api.h +++ b/browser/extensions/api/brave_rewards_api.h @@ -191,20 +191,29 @@ class BraveRewardsCreateRewardsWalletFunction : public ExtensionFunction { ResponseAction Run() override; private: - void CreateRewardsWalletCallback(ledger::mojom::Result result); + void CreateRewardsWalletCallback( + ledger::mojom::CreateRewardsWalletResult result); }; -class BraveRewardsGetRewardsWalletFunction : public ExtensionFunction { +class BraveRewardsGetAvailableCountriesFunction : public ExtensionFunction { public: - DECLARE_EXTENSION_FUNCTION("braveRewards.getRewardsWallet", UNKNOWN) + DECLARE_EXTENSION_FUNCTION("braveRewards.getAvailableCountries", UNKNOWN) protected: - ~BraveRewardsGetRewardsWalletFunction() override; + ~BraveRewardsGetAvailableCountriesFunction() override; + + private: + void GetAvailableCountriesCallback(std::vector countries); ResponseAction Run() override; +}; - private: - void GetRewardsWalletCallback(ledger::mojom::RewardsWalletPtr rewards_wallet); +class BraveRewardsGetDeclaredCountryFunction : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("braveRewards.getDeclaredCountry", UNKNOWN) + protected: + ~BraveRewardsGetDeclaredCountryFunction() override; + ResponseAction Run() override; }; class BraveRewardsGetBalanceReportFunction : public ExtensionFunction { diff --git a/browser/ui/views/brave_actions/brave_rewards_action_view.cc b/browser/ui/views/brave_actions/brave_rewards_action_view.cc index 34113e68d322..3550cafc35da 100644 --- a/browser/ui/views/brave_actions/brave_rewards_action_view.cc +++ b/browser/ui/views/brave_actions/brave_rewards_action_view.cc @@ -158,6 +158,10 @@ BraveRewardsActionView::BraveRewardsActionView(Browser* browser) brave_rewards::prefs::kBadgeText, base::BindRepeating(&BraveRewardsActionView::OnPreferencesChanged, base::Unretained(this))); + pref_change_registrar_.Add( + brave_rewards::prefs::kDeclaredGeo, + base::BindRepeating(&BraveRewardsActionView::OnPreferencesChanged, + base::Unretained(this))); browser_->tab_strip_model()->AddObserver(this); @@ -361,8 +365,21 @@ BraveRewardsActionView::GetBadgeTextAndBackground() { } size_t BraveRewardsActionView::GetRewardsNotificationCount() { - auto* service = GetNotificationService(); - return service ? service->GetAllNotifications().size() : 0; + size_t count = 0; + + if (auto* service = GetNotificationService()) { + count += service->GetAllNotifications().size(); + } + + // Increment the notification count if the user has enabled Rewards but has + // not declared a country. + auto* prefs = browser_->profile()->GetPrefs(); + if (prefs->GetBoolean(brave_rewards::prefs::kEnabled) && + prefs->GetString(brave_rewards::prefs::kDeclaredGeo).empty()) { + ++count; + } + + return count; } bool BraveRewardsActionView::UpdatePublisherStatus() { diff --git a/browser/ui/webui/brave_rewards/rewards_panel_ui.cc b/browser/ui/webui/brave_rewards/rewards_panel_ui.cc index 599964e4bf22..b537486518dc 100644 --- a/browser/ui/webui/brave_rewards/rewards_panel_ui.cc +++ b/browser/ui/webui/brave_rewards/rewards_panel_ui.cc @@ -95,8 +95,27 @@ static constexpr webui::LocalizedString kStrings[] = { IDS_REWARDS_NOTIFICATION_WALLET_DISCONNECTED_TITLE}, {"notificationTokenGrantTitle", IDS_REWARDS_NOTIFICATION_TOKEN_GRANT_TITLE}, {"ok", IDS_REWARDS_PANEL_OK}, + {"onboardingClose", IDS_BRAVE_REWARDS_ONBOARDING_CLOSE}, + {"onboardingContinue", IDS_BRAVE_REWARDS_ONBOARDING_CONTINUE}, {"onboardingEarnHeader", IDS_BRAVE_REWARDS_ONBOARDING_EARN_HEADER}, {"onboardingEarnText", IDS_BRAVE_REWARDS_ONBOARDING_EARN_TEXT}, + {"onboardingErrorHeader", IDS_BRAVE_REWARDS_ONBOARDING_ERROR_HEADER}, + {"onboardingErrorHeaderDisabled", + IDS_BRAVE_REWARDS_ONBOARDING_ERROR_HEADER_DISABLED}, + {"onboardingErrorText", IDS_BRAVE_REWARDS_ONBOARDING_ERROR_TEXT}, + {"onboardingErrorTextAlreadyDeclared", + IDS_BRAVE_REWARDS_ONBOARDING_ERROR_TEXT_ALREADY_DECLARED}, + {"onboardingErrorTextDeclareCountry", + IDS_BRAVE_REWARDS_ONBOARDING_ERROR_TEXT_DECLARE_COUNTRY}, + {"onboardingErrorTextDisabled", + IDS_BRAVE_REWARDS_ONBOARDING_ERROR_TEXT_DISABLED}, + {"onboardingGeoHeader", IDS_BRAVE_REWARDS_ONBOARDING_GEO_HEADER}, + {"onboardingGeoSuccessHeader", + IDS_BRAVE_REWARDS_ONBOARDING_GEO_SUCCESS_HEADER}, + {"onboardingGeoSuccessText", IDS_BRAVE_REWARDS_ONBOARDING_GEO_SUCCESS_TEXT}, + {"onboardingGeoText", IDS_BRAVE_REWARDS_ONBOARDING_GEO_TEXT}, + {"onboardingGeoTextDeclareCountry", + IDS_BRAVE_REWARDS_ONBOARDING_GEO_TEXT_DECLARE_COUNTRY}, {"onboardingPanelAcHeader", IDS_BRAVE_REWARDS_ONBOARDING_PANEL_AC_HEADER}, {"onboardingPanelAcText", IDS_BRAVE_REWARDS_ONBOARDING_PANEL_AC_TEXT}, {"onboardingPanelAdsHeader", IDS_BRAVE_REWARDS_ONBOARDING_PANEL_ADS_HEADER}, @@ -137,6 +156,8 @@ static constexpr webui::LocalizedString kStrings[] = { IDS_BRAVE_REWARDS_ONBOARDING_PANEL_WELCOME_HEADER}, {"onboardingPanelWelcomeText", IDS_BRAVE_REWARDS_ONBOARDING_PANEL_WELCOME_TEXT}, + {"onboardingSave", IDS_BRAVE_REWARDS_ONBOARDING_SAVE}, + {"onboardingSelectCountry", IDS_BRAVE_REWARDS_ONBOARDING_SELECT_COUNTRY}, {"onboardingSetupAdsHeader", IDS_BRAVE_REWARDS_ONBOARDING_SETUP_ADS_HEADER}, {"onboardingSetupAdsSubheader", IDS_BRAVE_REWARDS_ONBOARDING_SETUP_ADS_SUBHEADER}, diff --git a/browser/ui/webui/brave_rewards_page_ui.cc b/browser/ui/webui/brave_rewards_page_ui.cc index 23d7abd8ebd7..b6babbf1bd95 100644 --- a/browser/ui/webui/brave_rewards_page_ui.cc +++ b/browser/ui/webui/brave_rewards_page_ui.cc @@ -1776,6 +1776,8 @@ void RewardsDOMHandler::OnRewardsWalletUpdated() { GetAdsData(base::Value::List()); GetAutoContributeProperties(base::Value::List()); GetOnboardingStatus(base::Value::List()); + GetExternalWallet(base::Value::List()); + GetCountryCode(base::Value::List()); } void RewardsDOMHandler::OnUnblindedTokensReady( @@ -1946,14 +1948,13 @@ void RewardsDOMHandler::GetAllMonthlyReportIds(const base::Value::List& args) { } void RewardsDOMHandler::GetCountryCode(const base::Value::List& args) { - AllowJavascript(); - - const std::string locale = - brave_l10n::LocaleHelper::GetInstance()->GetLocale(); - const std::string country_code = brave_l10n::GetCountryCode(locale); + if (!rewards_service_) { + return; + } + AllowJavascript(); CallJavascriptFunction("brave_rewards.countryCode", - base::Value(country_code)); + base::Value(rewards_service_->GetCountryCode())); } void RewardsDOMHandler::CompleteReset(const base::Value::List& args) { diff --git a/browser/ui/webui/brave_webui_source.cc b/browser/ui/webui/brave_webui_source.cc index 7a7a9be49de1..076e1af6f602 100644 --- a/browser/ui/webui/brave_webui_source.cc +++ b/browser/ui/webui/brave_webui_source.cc @@ -262,6 +262,7 @@ void CustomizeWebUIHTMLSource(content::WebUI* web_ui, { "rewardsBraveRewards", IDS_REWARDS_WIDGET_BRAVE_REWARDS }, { "rewardsClaimRewards", IDS_REWARDS_WIDGET_CLAIM_REWARDS }, { "rewardsClaimTokens", IDS_REWARDS_WIDGET_CLAIM_TOKENS }, + { "rewardsContinue", IDS_REWARDS_WIDGET_CONTINUE}, { "rewardsEarning", IDS_REWARDS_WIDGET_EARNING }, { "rewardsEarningInfoText", IDS_REWARDS_WIDGET_EARNING_INFO_TEXT }, { "rewardsExchangeValueNote", IDS_REWARDS_WIDGET_EXCHANGE_VALUE_NOTE }, @@ -277,6 +278,8 @@ void CustomizeWebUIHTMLSource(content::WebUI* web_ui, { "rewardsPaymentPending", IDS_REWARDS_PAYMENT_PENDING }, { "rewardsPaymentProcessing", IDS_REWARDS_PAYMENT_PROCESSING }, { "rewardsProgress", IDS_REWARDS_WIDGET_PROGRESS }, + { "rewardsSelectCountryHeader", IDS_REWARDS_WIDGET_SELECT_COUNTRY_HEADER}, // NOLINT + { "rewardsSelectCountryText", IDS_REWARDS_WIDGET_SELECT_COUNTRY_TEXT}, { "rewardsSettings", IDS_REWARDS_WIDGET_SETTINGS }, { "rewardsSponsoredImageEarningText", IDS_REWARDS_WIDGET_SPONSORED_IMAGE_EARNING_TEXT }, // NOLINT { "rewardsSponsoredImageOptInText", IDS_REWARDS_WIDGET_SPONSORED_IMAGE_OPT_IN_TEXT }, // NOLINT diff --git a/common/extensions/api/brave_rewards.json b/common/extensions/api/brave_rewards.json index c8d39ea592c1..15f3c5a35e09 100644 --- a/common/extensions/api/brave_rewards.json +++ b/common/extensions/api/brave_rewards.json @@ -775,44 +775,59 @@ "type": "function", "description": "Creates a Rewards wallet for the current profile", "parameters": [ + { + "type": "string", + "name": "country" + }, { "type": "function", "name": "callback", "parameters": [ { - "type": "number", - "name": "errorCode" + "type": "string", + "name": "result" } ] } ] }, { - "name": "getRewardsWallet", + "name": "getAvailableCountries", "type": "function", - "description": "Returns the Rewards wallet information for the current profile", + "description": "", "parameters": [ { - "type": "function", "name": "callback", + "type": "function", "parameters": [ { - "name": "wallet", - "type": "object", - "optional": true, - "properties": { - "paymentId": { - "type": "string" - }, - "country": { - "type": "string" - } + "name": "countries", + "type": "array", + "items": { + "type": "string" } } ] } ] }, + { + "name": "getDeclaredCountry", + "type": "function", + "description": "", + "parameters": [ + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "country", + "type": "string" + } + ] + } + ] + }, { "name": "getBalanceReport", "type": "function", diff --git a/components/brave_new_tab_ui/api/initialData.ts b/components/brave_new_tab_ui/api/initialData.ts index fe6e8368a04e..747fcb21b8a3 100644 --- a/components/brave_new_tab_ui/api/initialData.ts +++ b/components/brave_new_tab_ui/api/initialData.ts @@ -28,6 +28,7 @@ export type InitialData = { export type PreInitialRewardsData = { rewardsEnabled: boolean + declaredCountry: string enabledAds: boolean adsSupported: boolean needsBrowserUpgradeToServeAds: boolean @@ -134,12 +135,15 @@ export async function getInitialData (): Promise { export async function getRewardsPreInitialData (): Promise { const [ rewardsEnabled, + declaredCountry, enabledAds, adsSupported, adsData ] = await Promise.all([ new Promise( (resolve) => chrome.braveRewards.getRewardsEnabled(resolve)), + new Promise( + (resolve) => chrome.braveRewards.getDeclaredCountry(resolve)), new Promise( (resolve) => chrome.braveRewards.getAdsEnabled(resolve)), new Promise( @@ -151,6 +155,7 @@ export async function getRewardsPreInitialData (): Promise void - onEnableRewards: () => void - onEnableAds: () => void onDismissNotification: (id: string) => void } @@ -96,9 +95,18 @@ export const RewardsWidget = createWidget((props: RewardsProps) => { } } + const openRewardsPanel = () => { + chrome.braveRewards.openRewardsPanel() + } + + const enableAds = () => { + chrome.braveRewards.enableAds() + } + return ( { earningsThisMonth={adsInfo ? adsInfo.earningsThisMonth : 0} earningsLastMonth={adsInfo ? adsInfo.earningsLastMonth : 0} contributionsThisMonth={props.totalContribution} - onEnableRewards={props.onEnableRewards} - onEnableAds={props.onEnableAds} + onEnableRewards={openRewardsPanel} + onEnableAds={enableAds} + onSelectCountry={openRewardsPanel} onClaimGrant={onClaimGrant} /> ) diff --git a/components/brave_new_tab_ui/containers/newTab/index.tsx b/components/brave_new_tab_ui/containers/newTab/index.tsx index e53d8624d635..5b1183aaf38c 100644 --- a/components/brave_new_tab_ui/containers/newTab/index.tsx +++ b/components/brave_new_tab_ui/containers/newTab/index.tsx @@ -956,8 +956,6 @@ class NewTabPage extends React.Component { hideWidget={this.toggleShowRewards} showContent={showContent} onShowContent={this.setForegroundStackWidget.bind(this, 'rewards')} - onEnableRewards={this.startRewards} - onEnableAds={this.startRewards} onDismissNotification={this.dismissNotification} /> ) diff --git a/components/brave_new_tab_ui/containers/newTab/settings/sponsoredImagesToggle.tsx b/components/brave_new_tab_ui/containers/newTab/settings/sponsoredImagesToggle.tsx index 0bcaf0d96d82..529a0c0c1da3 100644 --- a/components/brave_new_tab_ui/containers/newTab/settings/sponsoredImagesToggle.tsx +++ b/components/brave_new_tab_ui/containers/newTab/settings/sponsoredImagesToggle.tsx @@ -49,7 +49,7 @@ const ToggleRow = styled.div` const DescriptionRow = styled.div` width: 100%; - display: grid; + display: grid; grid-template-rows: 16px 1fr; grid-template-columns: 16px 1fr; padding: 0 8px; @@ -86,6 +86,8 @@ const EnableRewardsButton = styled.button` color: white; cursor: pointer; margin-bottom: 8px; + font-family: var(--brave-font-family-non-serif); + font-weight: 600; ` export default function SponsoredImageToggle ({ onChange, onEnableRewards, checked, disabled, rewardsEnabled: rewardEnabled, adsEnabled, canSupportAds }: Props) { diff --git a/components/brave_new_tab_ui/reducers/rewards_reducer.ts b/components/brave_new_tab_ui/reducers/rewards_reducer.ts index 19de0d8dc4d2..a51106cfb01f 100644 --- a/components/brave_new_tab_ui/reducers/rewards_reducer.ts +++ b/components/brave_new_tab_ui/reducers/rewards_reducer.ts @@ -86,6 +86,7 @@ const rewardsReducer: Reducer = (state: NewTab.State, rewardsState: { ...state.rewardsState, rewardsEnabled: preInitialRewardsDataPayload.rewardsEnabled, + declaredCountry: preInitialRewardsDataPayload.declaredCountry, enabledAds: preInitialRewardsDataPayload.enabledAds, adsSupported: preInitialRewardsDataPayload.adsSupported, needsBrowserUpgradeToServeAds: preInitialRewardsDataPayload.needsBrowserUpgradeToServeAds diff --git a/components/brave_new_tab_ui/storage/new_tab_storage.ts b/components/brave_new_tab_ui/storage/new_tab_storage.ts index 3132ea3691e2..585416c96d22 100644 --- a/components/brave_new_tab_ui/storage/new_tab_storage.ts +++ b/components/brave_new_tab_ui/storage/new_tab_storage.ts @@ -78,6 +78,7 @@ export const defaultState: NewTab.State = { externalWallet: undefined, dismissedNotifications: [], rewardsEnabled: false, + declaredCountry: '', enabledAds: false, needsBrowserUpgradeToServeAds: false, adsSupported: false, diff --git a/components/brave_rewards/browser/rewards_service.cc b/components/brave_rewards/browser/rewards_service.cc index 0b42a12867b1..bb7a2a521bab 100644 --- a/components/brave_rewards/browser/rewards_service.cc +++ b/components/brave_rewards/browser/rewards_service.cc @@ -41,6 +41,7 @@ void RewardsService::RegisterProfilePrefs(PrefRegistrySimple* registry) { registry->RegisterBooleanPref(prefs::kUserHasClaimedGrant, false); registry->RegisterTimePref(prefs::kAddFundsNotification, base::Time()); registry->RegisterBooleanPref(prefs::kEnabled, false); + registry->RegisterStringPref(prefs::kDeclaredGeo, ""); registry->RegisterTimePref(prefs::kAdsEnabledTimestamp, base::Time()); registry->RegisterTimeDeltaPref(prefs::kAdsEnabledTimeDelta, base::TimeDelta()); diff --git a/components/brave_rewards/browser/rewards_service.h b/components/brave_rewards/browser/rewards_service.h index bc015bf43cbd..2a4df29c3334 100644 --- a/components/brave_rewards/browser/rewards_service.h +++ b/components/brave_rewards/browser/rewards_service.h @@ -135,13 +135,25 @@ class RewardsService : public KeyedService { virtual bool IsInitialized() = 0; using CreateRewardsWalletCallback = - base::OnceCallback; + base::OnceCallback; // Creates a Rewards wallet for the current profile. If a Rewards wallet has // already been created, then the existing wallet information will be // returned. Ads and AC will be enabled if those prefs have not been // previously set. - virtual void CreateRewardsWallet(CreateRewardsWalletCallback callback) = 0; + virtual void CreateRewardsWallet(const std::string& country, + CreateRewardsWalletCallback callback) = 0; + + // Returns the country code associated with the user's Rewards profile. + virtual std::string GetCountryCode() const = 0; + + using GetAvailableCountriesCallback = + base::OnceCallback)>; + + // Asynchronously returns a vector of ISO country codes that the user can + // select when creating a Rewards ID. + virtual void GetAvailableCountries( + GetAvailableCountriesCallback callback) const = 0; virtual void GetRewardsParameters(GetRewardsParametersCallback callback) = 0; virtual void GetActivityInfoList(const uint32_t start, diff --git a/components/brave_rewards/browser/rewards_service_impl.cc b/components/brave_rewards/browser/rewards_service_impl.cc index 036307123890..a28d1a5a3030 100644 --- a/components/brave_rewards/browser/rewards_service_impl.cc +++ b/components/brave_rewards/browser/rewards_service_impl.cc @@ -25,6 +25,7 @@ #include "base/json/json_string_value_serializer.h" #include "base/json/json_writer.h" #include "base/logging.h" +#include "base/ranges/algorithm.h" #include "base/strings/escape.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" @@ -76,6 +77,7 @@ #include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/mojom/url_response_head.mojom.h" +#include "third_party/icu/source/common/unicode/locid.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/image/image.h" #include "url/gurl.h" @@ -282,6 +284,15 @@ std::string GetPrefPath(const std::string& name) { return base::StringPrintf("%s.%s", pref_prefix, name.c_str()); } +std::vector GetISOCountries() { + std::vector countries; + for (const char* const* country_pointer = icu::Locale::getISOCountries(); + *country_pointer; ++country_pointer) { + countries.emplace_back(*country_pointer); + } + return countries; +} + template void DeferCallback(base::Location location, Callback callback, Args&&... args) { base::SequencedTaskRunnerHandle::Get()->PostTask( @@ -554,30 +565,37 @@ void RewardsServiceImpl::MaybeShowAddFundsNotification( } void RewardsServiceImpl::CreateRewardsWallet( + const std::string& country, CreateRewardsWalletCallback callback) { + using ledger::mojom::CreateRewardsWalletResult; + auto on_start = [](base::WeakPtr self, + std::string country, CreateRewardsWalletCallback callback) { if (!self) { - std::move(callback).Run(ledger::mojom::Result::LEDGER_ERROR); + std::move(callback).Run(CreateRewardsWalletResult::kUnexpected); return; } auto on_created = [](base::WeakPtr self, + std::string country, CreateRewardsWalletCallback callback, - ledger::mojom::Result result) { + CreateRewardsWalletResult result) { if (!self) { - std::move(callback).Run(ledger::mojom::Result::LEDGER_ERROR); + std::move(callback).Run(CreateRewardsWalletResult::kUnexpected); return; } - if (result != ledger::mojom::Result::LEDGER_OK) { + if (result != CreateRewardsWalletResult::kSuccess) { std::move(callback).Run(result); return; } + auto* prefs = self->profile_->GetPrefs(); + prefs->SetString(prefs::kDeclaredGeo, country); + // After successfully creating a Rewards wallet for the first time, // automatically enable Ads and AC. - auto* prefs = self->profile_->GetPrefs(); if (!prefs->GetBoolean(prefs::kEnabled)) { prefs->SetBoolean(prefs::kEnabled, true); prefs->SetBoolean(ads::prefs::kEnabled, true); @@ -600,18 +618,82 @@ void RewardsServiceImpl::CreateRewardsWallet( observer.OnRewardsWalletUpdated(); } - std::move(callback).Run(ledger::mojom::Result::LEDGER_OK); + std::move(callback).Run(result); }; self->bat_ledger_->CreateRewardsWallet( - base::BindOnce(on_created, self, std::move(callback))); + country, + base::BindOnce(on_created, self, country, std::move(callback))); }; - ready_->Post(FROM_HERE, - base::BindOnce(on_start, AsWeakPtr(), std::move(callback))); + ready_->Post(FROM_HERE, base::BindOnce(on_start, AsWeakPtr(), country, + std::move(callback))); StartLedgerProcessIfNecessary(); } +std::string RewardsServiceImpl::GetCountryCode() const { + auto* prefs = profile_->GetPrefs(); + std::string country = prefs->GetString(prefs::kDeclaredGeo); + if (!country.empty()) { + return country; + } + + int32_t country_id = country_id_; + if (!country_id) { + country_id = country_codes::GetCountryIDFromPrefs(prefs); + } + if (country_id < 0) { + return ""; + } + return {base::ToUpperASCII(static_cast(country_id >> 8)), + base::ToUpperASCII(static_cast(0xFF & country_id))}; +} + +void RewardsServiceImpl::GetAvailableCountries( + GetAvailableCountriesCallback callback) const { + static const std::vector kISOCountries = GetISOCountries(); + + if (!Connected()) { + return DeferCallback(FROM_HERE, std::move(callback), kISOCountries); + } + + auto on_external_wallet = [](GetAvailableCountriesCallback callback, + ledger::mojom::Result result, + ledger::mojom::ExternalWalletPtr wallet) { + // If the user is not currently linked to any wallet provider, then all ISO + // country codes are available. + if (!wallet || + wallet->status == ledger::mojom::WalletStatus::NOT_CONNECTED) { + return std::move(callback).Run(kISOCountries); + } + + // If the user is currently linked to a bitFlyer wallet, then the only + // available countries are |kBitflyerCountries|. + if (wallet->type == ledger::constant::kWalletBitflyer) { + return std::move(callback).Run(std::vector( + kBitflyerCountries.begin(), kBitflyerCountries.end())); + } + + // If the user is currently linked to any other external wallet provider, + // then remove |kBitflyerCountries| from the list of ISO countries. + static const std::vector kNonBitflyerCountries = []() { + auto countries = kISOCountries; + auto removed = + base::ranges::remove_if(countries, [](const std::string& country) { + return base::Contains(kBitflyerCountries, country); + }); + countries.erase(removed, countries.end()); + return countries; + }(); + + return std::move(callback).Run(kNonBitflyerCountries); + }; + + bat_ledger_->GetExternalWallet( + GetExternalWalletType(), + base::BindOnce(on_external_wallet, std::move(callback))); +} + void RewardsServiceImpl::GetActivityInfoList( uint32_t start, uint32_t limit, @@ -2945,23 +3027,7 @@ void RewardsServiceImpl::GetRewardsWalletPassphrase( } bool RewardsServiceImpl::IsBitFlyerRegion() const { - int32_t current_country = country_id_; - - if (!current_country) { - current_country = - country_codes::GetCountryIDFromPrefs(profile_->GetPrefs()); - } - - for (const auto& country : kBitflyerCountries) { - if (country.length() == 2) { - const int id = - country_codes::CountryCharsToCountryID(country.at(0), country.at(1)); - - if (id == current_country) - return true; - } - } - return false; + return base::Contains(kBitflyerCountries, GetCountryCode()); } bool RewardsServiceImpl::IsValidWalletType( diff --git a/components/brave_rewards/browser/rewards_service_impl.h b/components/brave_rewards/browser/rewards_service_impl.h index 98dfce6c1134..3a84ffc77282 100644 --- a/components/brave_rewards/browser/rewards_service_impl.h +++ b/components/brave_rewards/browser/rewards_service_impl.h @@ -122,7 +122,13 @@ class RewardsServiceImpl : public RewardsService, std::unique_ptr notification_observer); - void CreateRewardsWallet(CreateRewardsWalletCallback callback) override; + void CreateRewardsWallet(const std::string& country, + CreateRewardsWalletCallback callback) override; + + std::string GetCountryCode() const override; + + void GetAvailableCountries( + GetAvailableCountriesCallback callback) const override; void GetRewardsParameters(GetRewardsParametersCallback callback) override; diff --git a/components/brave_rewards/browser/static_values.h b/components/brave_rewards/browser/static_values.h index 30fa3de5f8b3..b15216f863a2 100644 --- a/components/brave_rewards/browser/static_values.h +++ b/components/brave_rewards/browser/static_values.h @@ -9,8 +9,8 @@ #include #include +#include #include -#include #include "base/time/time.h" #include "bat/ledger/option_keys.h" @@ -18,7 +18,7 @@ namespace brave_rewards { -const std::vector kBitflyerCountries = { +const std::set kBitflyerCountries = { "JP" // ID: 19024 }; diff --git a/components/brave_rewards/browser/test/common/rewards_browsertest_response.cc b/components/brave_rewards/browser/test/common/rewards_browsertest_response.cc index db17b281690a..7795fcc1420e 100644 --- a/components/brave_rewards/browser/test/common/rewards_browsertest_response.cc +++ b/components/brave_rewards/browser/test/common/rewards_browsertest_response.cc @@ -7,14 +7,15 @@ #include #include "base/big_endian.h" +#include "base/containers/contains.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" +#include "bat/ledger/internal/common/request_util.h" #include "bat/ledger/internal/publisher/prefix_util.h" #include "bat/ledger/internal/publisher/protos/channel_response.pb.h" #include "bat/ledger/internal/publisher/protos/publisher_prefix_list.pb.h" -#include "bat/ledger/internal/common/request_util.h" #include "bat/ledger/internal/uphold/uphold_util.h" #include "brave/components/brave_rewards/browser/test/common/rewards_browsertest_network_util.h" #include "brave/components/brave_rewards/browser/test/common/rewards_browsertest_response.h" @@ -212,7 +213,14 @@ void RewardsBrowserTestResponse::Get( requests_.emplace_back(url, method); DCHECK(response_status_code && response); - if (url.find("/v3/wallet/brave") != std::string::npos) { + if (base::Contains(url, "/v4/wallets/")) { + *response = ""; + *response_status_code = net::HTTP_OK; + return; + } + + if (base::Contains(url, "/v3/wallet/brave") || + base::Contains(url, "/v4/wallets")) { *response = wallet_; *response_status_code = net::HTTP_CREATED; return; diff --git a/components/brave_rewards/browser/test/common/rewards_browsertest_util.cc b/components/brave_rewards/browser/test/common/rewards_browsertest_util.cc index 3de2587e8ca0..b92c3195e7dd 100644 --- a/components/brave_rewards/browser/test/common/rewards_browsertest_util.cc +++ b/components/brave_rewards/browser/test/common/rewards_browsertest_util.cc @@ -113,10 +113,12 @@ void CreateRewardsWallet(brave_rewards::RewardsServiceImpl* rewards_service) { base::RunLoop run_loop; bool success = false; rewards_service->CreateRewardsWallet( - base::BindLambdaForTesting([&](const ledger::mojom::Result result) { - success = result == ledger::mojom::Result::LEDGER_OK; - run_loop.Quit(); - })); + "US", base::BindLambdaForTesting( + [&](ledger::mojom::CreateRewardsWalletResult result) { + success = result == + ledger::mojom::CreateRewardsWalletResult::kSuccess; + run_loop.Quit(); + })); run_loop.Run(); diff --git a/components/brave_rewards/browser/test/rewards_browsertest.cc b/components/brave_rewards/browser/test/rewards_browsertest.cc index 9c4d4acebbf5..c582cc53a10e 100644 --- a/components/brave_rewards/browser/test/rewards_browsertest.cc +++ b/components/brave_rewards/browser/test/rewards_browsertest.cc @@ -38,6 +38,31 @@ namespace rewards_browsertest { +constexpr char kSelectCountryScript[] = R"( + const select = document.querySelector('[data-test-id=country-select]'); + select.value = 'US'; + select.dispatchEvent(new Event("change", { bubbles: true })); + true; +)"; + +class WalletUpdatedWaiter : public brave_rewards::RewardsServiceObserver { + public: + explicit WalletUpdatedWaiter(brave_rewards::RewardsService* rewards_service) + : rewards_service_(rewards_service) { + rewards_service_->AddObserver(this); + } + + ~WalletUpdatedWaiter() override { rewards_service_->RemoveObserver(this); } + + void OnRewardsWalletUpdated() override { run_loop_.Quit(); } + + void Wait() { run_loop_.Run(); } + + private: + base::RunLoop run_loop_; + brave_rewards::RewardsService* rewards_service_; +}; + class RewardsBrowserTest : public InProcessBrowserTest { public: RewardsBrowserTest() { @@ -376,8 +401,11 @@ IN_PROC_BROWSER_TEST_F(RewardsBrowserTest, EnableRewardsWithBalance) { prefs->SetBoolean(brave_rewards::prefs::kAutoContributeEnabled, false); base::RunLoop run_loop; - rewards_service_->CreateRewardsWallet(base::BindLambdaForTesting( - [&run_loop](ledger::mojom::Result) { run_loop.Quit(); })); + rewards_service_->CreateRewardsWallet( + "", base::BindLambdaForTesting( + [&run_loop](ledger::mojom::CreateRewardsWalletResult) { + run_loop.Quit(); + })); run_loop.Run(); // Ensure that AC is not enabled @@ -385,4 +413,49 @@ IN_PROC_BROWSER_TEST_F(RewardsBrowserTest, EnableRewardsWithBalance) { EXPECT_FALSE(prefs->GetBoolean(brave_rewards::prefs::kAutoContributeEnabled)); } +IN_PROC_BROWSER_TEST_F(RewardsBrowserTest, GeoDeclarationNewUser) { + auto* prefs = browser()->profile()->GetPrefs(); + prefs->SetBoolean(brave_rewards::prefs::kEnabled, false); + EXPECT_EQ(prefs->GetString(brave_rewards::prefs::kDeclaredGeo), ""); + + auto popup_contents = context_helper_->OpenRewardsPopup(); + ASSERT_TRUE(popup_contents); + + rewards_browsertest_util::WaitForElementThenClick( + popup_contents.get(), "[data-test-id=opt-in-button]"); + + rewards_browsertest_util::WaitForElementToAppear( + popup_contents.get(), "[data-test-id=country-select]"); + + WalletUpdatedWaiter waiter(rewards_service_); + EXPECT_EQ(true, content::EvalJs(popup_contents.get(), kSelectCountryScript)); + rewards_browsertest_util::WaitForElementThenClick( + popup_contents.get(), "[data-test-id=select-country-button]"); + waiter.Wait(); + + EXPECT_EQ(prefs->GetString(brave_rewards::prefs::kDeclaredGeo), "US"); + EXPECT_TRUE(prefs->GetBoolean(brave_rewards::prefs::kEnabled)); +} + +IN_PROC_BROWSER_TEST_F(RewardsBrowserTest, GeoDeclarationExistingUser) { + rewards_browsertest_util::CreateRewardsWallet(rewards_service_); + auto* prefs = browser()->profile()->GetPrefs(); + prefs->SetString(brave_rewards::prefs::kDeclaredGeo, ""); + + auto popup_contents = context_helper_->OpenRewardsPopup(); + ASSERT_TRUE(popup_contents); + + rewards_browsertest_util::WaitForElementToAppear( + popup_contents.get(), "[data-test-id=select-country-button]"); + + WalletUpdatedWaiter waiter(rewards_service_); + EXPECT_EQ(true, content::EvalJs(popup_contents.get(), kSelectCountryScript)); + rewards_browsertest_util::WaitForElementThenClick( + popup_contents.get(), "[data-test-id=select-country-button]"); + waiter.Wait(); + + EXPECT_EQ(prefs->GetString(brave_rewards::prefs::kDeclaredGeo), "US"); + EXPECT_TRUE(prefs->GetBoolean(brave_rewards::prefs::kEnabled)); +} + } // namespace rewards_browsertest diff --git a/components/brave_rewards/browser/test/rewards_p3a_browsertest.cc b/components/brave_rewards/browser/test/rewards_p3a_browsertest.cc index 1276579583f5..6d075e4afdb4 100644 --- a/components/brave_rewards/browser/test/rewards_p3a_browsertest.cc +++ b/components/brave_rewards/browser/test/rewards_p3a_browsertest.cc @@ -109,11 +109,7 @@ class RewardsP3ABrowserTest : public InProcessBrowserTest, // turn on Ads and AC. browser()->profile()->GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, false); - - base::RunLoop run_loop; - rewards_service_->CreateRewardsWallet(base::BindLambdaForTesting( - [&run_loop](ledger::mojom::Result) { run_loop.Quit(); })); - run_loop.Run(); + rewards_browsertest_util::CreateRewardsWallet(rewards_service_); } content::WebContents* contents() { diff --git a/components/brave_rewards/common/pref_names.cc b/components/brave_rewards/common/pref_names.cc index ab35baee32e3..42c6e8394ac3 100644 --- a/components/brave_rewards/common/pref_names.cc +++ b/components/brave_rewards/common/pref_names.cc @@ -12,6 +12,7 @@ const char kDisabledByPolicy[] = "brave.rewards.disabled_by_policy"; const char kHideButton[] = "brave.hide_brave_rewards_button"; const char kShowButton[] = "brave.show_brave_rewards_button"; const char kEnabled[] = "brave.rewards.enabled"; +const char kDeclaredGeo[] = "brave.rewards.declared_geo"; const char kAdsEnabledTimeDelta[] = "brave.rewards.ads_enabled_time_delta"; const char kAdsEnabledTimestamp[] = "brave.rewards.ads_enabled_timestamp"; const char kNotifications[] = "brave.rewards.notifications"; diff --git a/components/brave_rewards/common/pref_names.h b/components/brave_rewards/common/pref_names.h index fc1e6c572e5a..28893b43a2a7 100644 --- a/components/brave_rewards/common/pref_names.h +++ b/components/brave_rewards/common/pref_names.h @@ -15,6 +15,7 @@ extern const char kDisabledByPolicy[]; extern const char kHideButton[]; // DEPRECATED extern const char kShowButton[]; extern const char kEnabled[]; // DEPRECATED +extern const char kDeclaredGeo[]; extern const char kAdsEnabledTimeDelta[]; extern const char kAdsEnabledTimestamp[]; extern const char kNotifications[]; diff --git a/components/brave_rewards/resources/extension/brave_rewards/_locales/en_US/messages.json b/components/brave_rewards/resources/extension/brave_rewards/_locales/en_US/messages.json index cb488d9b0861..aa042555f164 100644 --- a/components/brave_rewards/resources/extension/brave_rewards/_locales/en_US/messages.json +++ b/components/brave_rewards/resources/extension/brave_rewards/_locales/en_US/messages.json @@ -398,6 +398,14 @@ "message": "You have pending tips due to insufficient funds", "description": "" }, + "onboardingClose": { + "message": "Close", + "description": "" + }, + "onboardingContinue": { + "message": "Continue", + "description": "" + }, "onboardingEarnHeader": { "message": "Earn Tokens & Give Back", "description": "" @@ -406,6 +414,95 @@ "message": "Earn tokens by viewing privacy-respecting ads and support your favorite sites and content creators automatically.", "description": "" }, + "onboardingErrorHeader": { + "message": "Something went wrong", + "description": "" + }, + "onboardingErrorHeaderDisabled": { + "message": "Brave Rewards not available", + "description": "" + }, + "onboardingErrorText": { + "message": "Unfortunately, there was an error while trying to set up Brave Rewards. Please try again.", + "description": "" + }, + "onboardingErrorTextAlreadyDeclared": { + "message": "It looks like a country has already been set. You can $link_begin$contact support$link_end$ for help.", + "description": "", + "placeholders": { + "link_begin": { + "content": "$1" + }, + "link_end": { + "content": "$2" + } + } + }, + "onboardingErrorTextDeclareCountry": { + "message": "Unfortunately, there was an error while trying to set your country. Please try again.", + "description": "" + }, + "onboardingErrorTextDisabled": { + "message": "New signups for Brave Rewards are currently disabled in your region. However, you can always try again later. $link_begin$Learn more$link_end$", + "description": "", + "placeholders": { + "link_begin": { + "content": "$1" + }, + "link_end": { + "content": "$2" + } + } + }, + "onboardingGeoHeader": { + "message": "Select your country", + "description": "" + }, + "onboardingGeoSuccessHeader": { + "message": "Thank you!", + "description": "" + }, + "onboardingGeoSuccessText": { + "message": "Your Brave Rewards region has been set to $country$.", + "description": "", + "placeholders": { + "country": { + "content": "$1" + } + } + }, + "onboardingGeoText": { + "message": "Select your country so we can show you the right options and ads for your region. $link_begin$Privacy Policy$link_end$", + "description": "", + "placeholders": { + "link_begin": { + "content": "$1" + }, + "link_end": { + "content": "$2" + } + } + }, + "onboardingGeoTextDeclareCountry": { + "message": "To continue using Brave Rewards, select your country so we can show you the right options and ads for your region. $link_begin$Privacy Policy$link_end$", + "description": "", + "placeholders": { + "link_begin": { + "content": "$1" + }, + "link_end": { + "content": "$2" + } + } + }, + "onboardingSave": { + "message": "Save", + "description": "" + }, + "onboardingSelectCountry": { + "message": "Select your country", + "description": "" + }, "onboardingSetupAdsHeader": { "message": "How often do you want to see ads?", "description": "" diff --git a/components/brave_rewards/resources/extension/brave_rewards/background/reducers/rewards_panel_reducer.ts b/components/brave_rewards/resources/extension/brave_rewards/background/reducers/rewards_panel_reducer.ts index cadd8e56a3a7..4c61dedc3cc3 100644 --- a/components/brave_rewards/resources/extension/brave_rewards/background/reducers/rewards_panel_reducer.ts +++ b/components/brave_rewards/resources/extension/brave_rewards/background/reducers/rewards_panel_reducer.ts @@ -258,7 +258,7 @@ export const rewardsPanelReducer: Reducer = case types.SAVE_ONBOARDING_RESULT: { state = { ...state, showOnboarding: false } if (payload.result === 'opted-in') { - chrome.braveRewards.createRewardsWallet(() => {}) + chrome.braveRewards.createRewardsWallet('', () => {}) } onboardingCompletedStore.save() break diff --git a/components/brave_rewards/resources/rewards_panel/components/panel_overlays.tsx b/components/brave_rewards/resources/rewards_panel/components/panel_overlays.tsx index 3bdcebc8b9da..cf5402eb98bb 100644 --- a/components/brave_rewards/resources/rewards_panel/components/panel_overlays.tsx +++ b/components/brave_rewards/resources/rewards_panel/components/panel_overlays.tsx @@ -5,7 +5,7 @@ import * as React from 'react' import { HostContext, useHostListener } from '../lib/host_context' -import { RewardsOptInModal, RewardsTourModal } from '../../shared/components/onboarding' +import { OnboardingResult, RewardsOptInModal, RewardsTourModal } from '../../shared/components/onboarding' import { AdaptiveCaptchaView } from '../../rewards_panel/components/adaptive_captcha_view' import { GrantCaptchaModal } from './grant_captcha_modal' import { NotificationOverlay } from './notification_overlay' @@ -37,6 +37,10 @@ export function PanelOverlays () { React.useState(host.state.requestedView) const [rewardsEnabled, setRewardsEnabled] = React.useState(host.state.rewardsEnabled) + const [declaredCountry, setDeclaredCountry] = + React.useState(host.state.declaredCountry) + const [availableCountries, setAvailableCountries] = + React.useState(host.state.availableCountries) const [settings, setSettings] = React.useState(host.state.settings) const [options, setOptions] = React.useState(host.state.options) const [externalWalletProviders, setExternalWalletProviders] = @@ -50,10 +54,16 @@ export function PanelOverlays () { const [showTour, setShowTour] = React.useState(false) const [notificationsHidden, setNotificationsHidden] = React.useState(false) + const [onboardingResult, setOnboardingResult] = + React.useState(null) + + const needsCountry = rewardsEnabled && !declaredCountry useHostListener(host, (state) => { setRequestedView(state.requestedView) setRewardsEnabled(state.rewardsEnabled) + setDeclaredCountry(state.declaredCountry) + setAvailableCountries(state.availableCountries) setSettings(state.settings) setOptions(state.options) setExternalWalletProviders(state.externalWalletProviders) @@ -72,11 +82,6 @@ export function PanelOverlays () { setShowTour(!showTour) } - function onEnable () { - host.enableRewards() - setShowTour(true) - } - if (showTour) { const onVerifyWalletClick = () => { host.handleExternalWalletAction('verify') @@ -100,10 +105,31 @@ export function PanelOverlays () { ) } - if (!rewardsEnabled) { + if (onboardingResult || !rewardsEnabled || needsCountry) { + const onHideResult = () => { + setOnboardingResult(null) + } + + const onEnable = (country: string) => { + host.enableRewards(country).then((result) => { + setOnboardingResult(result) + if (!rewardsEnabled && result === 'success') { + setOnboardingResult(null) + setShowTour(true) + } + }) + } + return ( - + ) } diff --git a/components/brave_rewards/resources/rewards_panel/lib/extension_api_adapter.ts b/components/brave_rewards/resources/rewards_panel/lib/extension_api_adapter.ts index 587ffd077bd6..3c8034ba10af 100644 --- a/components/brave_rewards/resources/rewards_panel/lib/extension_api_adapter.ts +++ b/components/brave_rewards/resources/rewards_panel/lib/extension_api_adapter.ts @@ -6,6 +6,7 @@ import { Notification } from '../../shared/components/notifications' import { GrantInfo } from '../../shared/lib/grant_info' import { ProviderPayoutStatus } from '../../shared/lib/provider_payout_status' import { RewardsSummaryData } from '../../shared/components/wallet_card' +import { OnboardingResult } from '../../shared/components/onboarding' import { mapNotification } from './notification_adapter' import { @@ -191,8 +192,34 @@ export function getRewardsEnabled () { }) } -export function onRewardsEnabled (callback: () => void) { - chrome.braveRewards.onRewardsWalletUpdated.addListener(() => { callback() }) +export function getDeclaredCountry () { + return new Promise((resolve) => { + chrome.braveRewards.getDeclaredCountry(resolve) + }) +} + +export function createRewardsWallet (country: string) { + return new Promise((resolve) => { + chrome.braveRewards.createRewardsWallet(country, (result) => { + switch (result) { + case 'success': + case 'unexpected-error': + case 'wallet-generation-disabled': + case 'country-already-declared': + resolve(result) + break + default: + resolve('unexpected-error') + break + } + }) + }) +} + +export function getAvailableCountries () { + return new Promise((resolve) => { + chrome.braveRewards.getAvailableCountries(resolve) + }) } function getMonthlyTipAmount (publisherKey: string) { diff --git a/components/brave_rewards/resources/rewards_panel/lib/extension_host.ts b/components/brave_rewards/resources/rewards_panel/lib/extension_host.ts index 83a593723742..72ce7564b39f 100644 --- a/components/brave_rewards/resources/rewards_panel/lib/extension_host.ts +++ b/components/brave_rewards/resources/rewards_panel/lib/extension_host.ts @@ -302,9 +302,7 @@ export function createHost (): Host { }) // Update user settings and other data after rewards has been enabled. - apiAdapter.onRewardsEnabled(() => { - stateManager.update({ rewardsEnabled: true }) - + chrome.braveRewards.onRewardsWalletUpdated.addListener(() => { loadPanelData().catch(console.error) }) @@ -333,6 +331,12 @@ export function createHost (): Host { apiAdapter.getRewardsEnabled().then((rewardsEnabled) => { stateManager.update({ rewardsEnabled }) }), + apiAdapter.getDeclaredCountry().then((declaredCountry) => { + stateManager.update({ declaredCountry }) + }), + apiAdapter.getAvailableCountries().then((availableCountries) => { + stateManager.update({ availableCountries }) + }), apiAdapter.getRewardsBalance().then((balance) => { stateManager.update({ balance }) }), @@ -396,12 +400,8 @@ export function createHost (): Host { getString, - enableRewards () { - chrome.braveRewards.createRewardsWallet((errorCode) => { - if (!errorCode) { - stateManager.update({ rewardsEnabled: true }) - } - }) + enableRewards (country: string) { + return apiAdapter.createRewardsWallet(country) }, openAdaptiveCaptchaSupport () { diff --git a/components/brave_rewards/resources/rewards_panel/lib/initial_state.ts b/components/brave_rewards/resources/rewards_panel/lib/initial_state.ts index c8468fc4d414..6545b99aca39 100644 --- a/components/brave_rewards/resources/rewards_panel/lib/initial_state.ts +++ b/components/brave_rewards/resources/rewards_panel/lib/initial_state.ts @@ -20,6 +20,7 @@ export function getInitialState (): HostState { autoContributeAmounts: [] }, grantCaptchaInfo: null, + adaptiveCaptchaInfo: null, exchangeInfo: { currency: 'USD', rate: 0 @@ -42,6 +43,7 @@ export function getInitialState (): HostState { pendingTips: 0 }, notifications: [], - adaptiveCaptchaInfo: null + availableCountries: [], + declaredCountry: '' } } diff --git a/components/brave_rewards/resources/rewards_panel/lib/interfaces.ts b/components/brave_rewards/resources/rewards_panel/lib/interfaces.ts index 2a8a21fec798..4bcb1069eefb 100644 --- a/components/brave_rewards/resources/rewards_panel/lib/interfaces.ts +++ b/components/brave_rewards/resources/rewards_panel/lib/interfaces.ts @@ -6,6 +6,7 @@ import { ExternalWallet, ExternalWalletProvider } from '../../shared/lib/externa import { GrantInfo } from '../../shared/lib/grant_info' import { ProviderPayoutStatus } from '../../shared/lib/provider_payout_status' import { PublisherPlatform } from '../../shared/lib/publisher_platform' +import { OnboardingResult } from '../../shared/components/onboarding' import { ExternalWalletAction, RewardsSummaryData } from '../../shared/components/wallet_card' import { Notification, NotificationAction } from '../../shared/components/notifications' @@ -70,8 +71,8 @@ type RequestedView = 'rewards-tour' export interface HostState { openTime: number loading: boolean - requestedView: RequestedView | null rewardsEnabled: boolean + requestedView: RequestedView | null balance: number settings: Settings options: Options @@ -86,6 +87,8 @@ export interface HostState { externalWallet: ExternalWallet | null summaryData: RewardsSummaryData notifications: Notification[] + availableCountries: string[] + declaredCountry: string } export type HostListener = (state: HostState) => void @@ -96,7 +99,7 @@ export interface Host { state: HostState addListener: (callback: HostListener) => () => void getString: (key: string) => string - enableRewards: () => void + enableRewards: (country: string) => Promise openAdaptiveCaptchaSupport: () => void openRewardsSettings: () => void refreshPublisherStatus: () => void diff --git a/components/brave_rewards/resources/rewards_panel/stories/index.tsx b/components/brave_rewards/resources/rewards_panel/stories/index.tsx index 4f0dc9adbad4..ffa3bec601ea 100644 --- a/components/brave_rewards/resources/rewards_panel/stories/index.tsx +++ b/components/brave_rewards/resources/rewards_panel/stories/index.tsx @@ -39,7 +39,7 @@ function createHost (): Host { openTime: Date.now(), loading: false, requestedView: null, - rewardsEnabled: false, + rewardsEnabled: true, settings: { adsPerHour: 3, autoContributeEnabled: true, @@ -63,6 +63,10 @@ function createHost (): Host { type: 'ads' } }, + adaptiveCaptchaInfo: { + url: '', + status: 'pending' + }, externalWalletProviders: ['uphold', 'gemini'], balance: 10.2, exchangeInfo: { @@ -109,10 +113,8 @@ function createHost (): Host { timeStamp: Date.now() - 100 } ], - adaptiveCaptchaInfo: { - url: '', - status: 'pending' - } + availableCountries: ['US'], + declaredCountry: '' }) return { @@ -128,8 +130,10 @@ function createHost (): Host { enableRewards () { stateManager.update({ - rewardsEnabled: true + rewardsEnabled: true, + declaredCountry: 'US' }) + return Promise.resolve('success') }, setAutoContributeAmount (amount) { diff --git a/components/brave_rewards/resources/shared/components/icons/geo_pin_icon.tsx b/components/brave_rewards/resources/shared/components/icons/geo_pin_icon.tsx new file mode 100644 index 000000000000..643b317d57d0 --- /dev/null +++ b/components/brave_rewards/resources/shared/components/icons/geo_pin_icon.tsx @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' + +export function GeoPinIcon () { + return ( + + + + ) +} diff --git a/components/brave_rewards/resources/shared/components/newtab/rewards_card.style.ts b/components/brave_rewards/resources/shared/components/newtab/rewards_card.style.ts index 848bc52a9f96..789d810dd1d6 100644 --- a/components/brave_rewards/resources/shared/components/newtab/rewards_card.style.ts +++ b/components/brave_rewards/resources/shared/components/newtab/rewards_card.style.ts @@ -60,6 +60,10 @@ export const rewardsOptInHeader = styled.div` line-height: 20px; ` +export const selectCountry = styled.div` + margin: 16px -12px 0; +` + export const terms = styled.div` margin-top: 14px; padding-bottom: 10px; diff --git a/components/brave_rewards/resources/shared/components/newtab/rewards_card.tsx b/components/brave_rewards/resources/shared/components/newtab/rewards_card.tsx index 81161ae29dbf..571788bf3cde 100644 --- a/components/brave_rewards/resources/shared/components/newtab/rewards_card.tsx +++ b/components/brave_rewards/resources/shared/components/newtab/rewards_card.tsx @@ -17,6 +17,7 @@ import { TokenAmount } from '../token_amount' import { ExchangeAmount } from '../exchange_amount' import { NewTabLink } from '../new_tab_link' import { GrantOverlay } from './grant_overlay' +import { SelectCountryCard } from './select_country_card' import { PaymentStatusView, shouldRenderPendingRewards } from '../payment_status_view' import * as urls from '../../lib/rewards_urls' @@ -62,6 +63,7 @@ export function RewardsCardHeader () { interface Props { rewardsEnabled: boolean + declaredCountry: string adsEnabled: boolean adsSupported: boolean needsBrowserUpgradeToServeAds: boolean @@ -77,6 +79,7 @@ interface Props { externalWallet: ExternalWallet | null onEnableRewards: () => void onEnableAds: () => void + onSelectCountry: () => void onClaimGrant: () => void } @@ -202,6 +205,17 @@ export function RewardsCard (props: Props) { ) } + function renderCountrySelect () { + return ( + + + + + + + ) + } + function renderAdsOptIn () { if (props.adsEnabled || !props.adsSupported) { return null @@ -225,6 +239,10 @@ export function RewardsCard (props: Props) { return renderRewardsOptIn() } + if (!props.declaredCountry) { + return renderCountrySelect() + } + return ( diff --git a/components/brave_rewards/resources/shared/components/newtab/select_country_card.style.ts b/components/brave_rewards/resources/shared/components/newtab/select_country_card.style.ts new file mode 100644 index 000000000000..29b1e0066771 --- /dev/null +++ b/components/brave_rewards/resources/shared/components/newtab/select_country_card.style.ts @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import styled from 'styled-components' + +import * as mixins from '../../lib/css_mixins' + +export const root = styled.div` + padding: 20px 16px; + font-family: var(--brave-font-heading); + text-align: center; + background: linear-gradient(137.04deg, #346FE1 33.4%, #5844C3 82.8%); + border-radius: 8px; + color: var(--brave-palette-white);; +` + +export const header = styled.div` + font-weight: 600; + font-size: 15px; + line-height: 20px; + + .icon { + vertical-align: middle; + width: 16px; + margin-right: 5px; + margin-bottom: 3px; + } +` + +export const text = styled.div` + margin-top: 9px; + font-size: 12px; + line-height: 18px; + + a { + color: var(--brave-palette-white); + text-decoration: underline; + padding-left: 4px; + } +` + +export const enable = styled.div` + margin-top: 16px; + + button { + ${mixins.buttonReset} + color: var(--brave-palette-white); + background: rgba(255, 255, 255, 0.2); + border-radius: 48px; + width: 100%; + padding: 6px; + font-size: 13px; + line-height: 20px; + font-weight: 600; + font-size: 12px; + line-height: 20px; + cursor: pointer; + + &:active { + background: rgba(255, 255, 255, 0.3); + } + } +` + +export const terms = styled.div` + margin-top: 16px; + font-size: 11px; + line-height: 16px; + color: rgba(255, 255, 255, 0.75); + + a { + color: var(--brave-color-white); + text-decoration: underline; + } +` diff --git a/components/brave_rewards/resources/shared/components/newtab/select_country_card.tsx b/components/brave_rewards/resources/shared/components/newtab/select_country_card.tsx new file mode 100644 index 000000000000..e8677d8441c5 --- /dev/null +++ b/components/brave_rewards/resources/shared/components/newtab/select_country_card.tsx @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' + +import { LocaleContext, formatMessage } from '../../lib/locale_context' +import { GeoPinIcon } from '../icons/geo_pin_icon' +import { TermsOfService } from '../terms_of_service' +import { NewTabLink } from '../new_tab_link' +import { privacyPolicyURL } from '../../lib/rewards_urls' + +import * as style from './select_country_card.style' + +interface Props { + onContinue: () => void +} + +export function SelectCountryCard (props: Props) { + const { getString } = React.useContext(LocaleContext) + return ( + + + {getString('rewardsSelectCountryHeader')} + + + { + formatMessage(getString('rewardsSelectCountryText'), { + tags: { + $1: (content) => + + {content} + + } + }) + } + + + + + + + + + ) +} diff --git a/components/brave_rewards/resources/shared/components/newtab/stories/index.tsx b/components/brave_rewards/resources/shared/components/newtab/stories/index.tsx index 5cb616c86d48..6ee535a48c0f 100644 --- a/components/brave_rewards/resources/shared/components/newtab/stories/index.tsx +++ b/components/brave_rewards/resources/shared/components/newtab/stories/index.tsx @@ -38,6 +38,7 @@ export function Card () {
diff --git a/components/brave_rewards/resources/shared/components/newtab/stories/locale_strings.ts b/components/brave_rewards/resources/shared/components/newtab/stories/locale_strings.ts index 0f97aee98f65..cb9d1488b32a 100644 --- a/components/brave_rewards/resources/shared/components/newtab/stories/locale_strings.ts +++ b/components/brave_rewards/resources/shared/components/newtab/stories/locale_strings.ts @@ -8,6 +8,7 @@ export const localeStrings = { rewardsBraveRewards: 'Brave Rewards', rewardsClaimRewards: 'Claim Rewards', rewardsClaimTokens: 'Claim Tokens', + rewardsContinue: 'Continue', rewardsEarning: 'Earning', rewardsEarningInfoText: 'The tokens you earn this month will begin processing on $1.', rewardsExchangeValueNote: 'This may vary based on market volatility.', @@ -23,6 +24,8 @@ export const localeStrings = { rewardsPaymentPending: 'Your $1 $2 rewards will arrive in $3', rewardsPaymentProcessing: 'Your $1 $2 rewards are on the way. Keep an eye out!', rewardsProgress: 'Progress', + rewardsSelectCountryHeader: 'Select your country', + rewardsSelectCountryText: 'To continue using Brave Rewards, select your country so we can show you the right options and ads for your region. $1Privacy Policy$2', rewardsSettings: 'Rewards settings', rewardsSponsoredImageEarningText: 'You’re earning tokens for viewing this sponsored image.', rewardsSponsoredImageOptInText: 'Earn tokens for viewing this image and support content creators.', diff --git a/components/brave_rewards/resources/shared/components/onboarding/assets/select_caret.svg b/components/brave_rewards/resources/shared/components/onboarding/assets/select_caret.svg new file mode 100644 index 000000000000..da8cfafb4a5d --- /dev/null +++ b/components/brave_rewards/resources/shared/components/onboarding/assets/select_caret.svg @@ -0,0 +1,3 @@ + + + diff --git a/components/brave_rewards/resources/shared/components/onboarding/country_select.tsx b/components/brave_rewards/resources/shared/components/onboarding/country_select.tsx new file mode 100644 index 000000000000..d13a7ec25b2d --- /dev/null +++ b/components/brave_rewards/resources/shared/components/onboarding/country_select.tsx @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' + +// @ts-expect-error: Requires TS 4.5 +const countryNames = new Intl.DisplayNames(undefined, { type: 'region' }) + +export function getCountryName (code: string) { + return countryNames.of(code) +} + +function getCountryOptions (countries: string[]) { + return countries + .map((code) => ({ code, name: countryNames.of(code) || '' })) + .filter((item) => Boolean(item.name)) + .sort((a, b) => a.name?.localeCompare(b.name)) +} + +interface Props { + countries: string[] + placeholderText: string + value: string + onChange: (country: string) => void + autoFocus?: boolean +} + +export function CountrySelect (props: Props) { + const onCountryChange = (event: React.FormEvent) => { + const { value } = event.currentTarget + if (value !== props.value) { + props.onChange(value) + } + } + + return ( + + ) +} diff --git a/components/brave_rewards/resources/shared/components/onboarding/css_mixins.ts b/components/brave_rewards/resources/shared/components/onboarding/css_mixins.ts new file mode 100644 index 000000000000..c6be25a0b222 --- /dev/null +++ b/components/brave_rewards/resources/shared/components/onboarding/css_mixins.ts @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +export const enableRewardsButton = ` + color: var(--brave-palette-white); + background: var(--brave-color-brandBat); + border: none; + padding: 10px 40px; + border-radius: 21px; + font-weight: 600; + font-size: 14px; + line-height: 21px; + cursor: pointer; + + &:active { + background: var(--brave-color-brandBatActive); + } + + &[disabled] { + background: var(--brave-palette-grey200); + cursor: default; + } +` diff --git a/components/brave_rewards/resources/shared/components/onboarding/icons/error_icon.tsx b/components/brave_rewards/resources/shared/components/onboarding/icons/error_icon.tsx new file mode 100644 index 000000000000..04c5b7ec1801 --- /dev/null +++ b/components/brave_rewards/resources/shared/components/onboarding/icons/error_icon.tsx @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' + +export function ErrorIcon () { + return ( + + + + ) +} diff --git a/components/brave_rewards/resources/shared/components/onboarding/icons/success_icon.tsx b/components/brave_rewards/resources/shared/components/onboarding/icons/success_icon.tsx new file mode 100644 index 000000000000..bef69559113f --- /dev/null +++ b/components/brave_rewards/resources/shared/components/onboarding/icons/success_icon.tsx @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' + +export function SuccessIcon () { + return ( + + + + ) +} diff --git a/components/brave_rewards/resources/shared/components/onboarding/index.ts b/components/brave_rewards/resources/shared/components/onboarding/index.ts index e526a92fd343..911eb7a1c8ec 100644 --- a/components/brave_rewards/resources/shared/components/onboarding/index.ts +++ b/components/brave_rewards/resources/shared/components/onboarding/index.ts @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { RewardsOptInModal } from './rewards_opt_in_modal' +export { RewardsOptInModal, OnboardingResult } from './rewards_opt_in_modal' export { RewardsTour } from './rewards_tour' export { RewardsTourModal } from './rewards_tour_modal' export { SettingsOptInForm } from './settings_opt_in_form' diff --git a/components/brave_rewards/resources/shared/components/onboarding/main_button.style.ts b/components/brave_rewards/resources/shared/components/onboarding/main_button.style.ts index 588cdf308fca..2aa5c2b0b7ba 100644 --- a/components/brave_rewards/resources/shared/components/onboarding/main_button.style.ts +++ b/components/brave_rewards/resources/shared/components/onboarding/main_button.style.ts @@ -19,5 +19,10 @@ export const root = styled.div` &:active { background: var(--brave-color-brandBatActive); } + + &[disabled] { + background: var(--brave-palette-grey200); + cursor: default; + } } ` diff --git a/components/brave_rewards/resources/shared/components/onboarding/main_button.tsx b/components/brave_rewards/resources/shared/components/onboarding/main_button.tsx index e371fcf1496f..bae550d55d33 100644 --- a/components/brave_rewards/resources/shared/components/onboarding/main_button.tsx +++ b/components/brave_rewards/resources/shared/components/onboarding/main_button.tsx @@ -8,13 +8,18 @@ import * as style from './main_button.style' interface Props { onClick: () => void + disabled?: boolean children: React.ReactNode } export function MainButton (props: Props) { return ( - diff --git a/components/brave_rewards/resources/shared/components/onboarding/rewards_opt_in_modal.style.ts b/components/brave_rewards/resources/shared/components/onboarding/rewards_opt_in_modal.style.ts index e9de14868780..06b0aa75f3b7 100644 --- a/components/brave_rewards/resources/shared/components/onboarding/rewards_opt_in_modal.style.ts +++ b/components/brave_rewards/resources/shared/components/onboarding/rewards_opt_in_modal.style.ts @@ -4,8 +4,11 @@ import styled from 'styled-components' +import selectCaret from './assets/select_caret.svg' import modalBackground from './assets/opt_in_modal_bg.svg' +import { enableRewardsButton } from './css_mixins' + export const root = styled.div` flex: 0 0 auto; width: 335px; @@ -18,26 +21,7 @@ export const root = styled.div` background-position: 4px -11px; background-size: auto 200px; box-shadow: 0px 0px 16px rgba(99, 105, 110, 0.2); - border-radius: 8px; -` - -export const close = styled.div` - color: var(--brave-palette-neutral600); - text-align: right; - - button { - margin: 0; - padding: 2px; - background: none; - border: none; - cursor: pointer; - } - - .icon { - display: block; - width: 14px; - height: auto; - } + border-radius: 16px; ` export const header = styled.div` @@ -45,7 +29,7 @@ export const header = styled.div` color: var(--brave-palette-black); font-weight: 600; font-size: 18px; - line-height: 22px; + line-height: 26px; .icon { vertical-align: middle; @@ -53,23 +37,88 @@ export const header = styled.div` margin-right: 5px; margin-bottom: 3px; } + + &.country-select .icon { + color: var(--brave-palette-neutral900); + height: 20px; + width: auto; + margin-right: 6px; + } + + &.onboarding-result .icon { + display: block; + margin: 0 auto 16px; + height: 24px; + width: auto; + } ` export const text = styled.div` - margin: 8px 6px 0; + margin-top: 8px; + padding: 0 23px; color: var(--brave-palette-neutral700); font-size: 14px; - line-height: 24px; + line-height: 23px; + + a { + padding-left: 4px; + text-decoration: underline; + } + + strong { + font-weight: 600; + } +` + +export const selectCountry = styled.div` + margin-top: 16px; + padding: 0 23px; + + select { + -webkit-appearance: none; + background: url(${selectCaret}) calc(100% - 12px) center no-repeat, #fff; + background-size: 12px; + width: 100%; + border-radius: 4px; + border: 1px solid var(--brave-palette-grey500); + color: var(--brave-palette-neutral900); + font-size: 12px; + line-height: 18px; + padding: 7px 36px 7px 12px; + + &.empty { + color: var(--brave-palette-neutral600); + } + } +` + +export const mainAction = styled.div` + margin-top: 24px; + padding: 0 23px; + + button { + ${enableRewardsButton} + width: 100%; + font-size: 13px; + line-height: 20px; + + .icon { + vertical-align: middle; + height: 18px; + width: auto; + } + } ` export const takeTour = styled.div` - margin-top: 20px; + margin-top: 18px; + margin-bottom: 42px; color: var(--brave-color-brandBat); button { font-weight: 600; - font-size: 14px; - line-height: 21px; + font-size: 13px; + line-height: 20px; border: 0; background: 0; margin: 0; @@ -78,14 +127,10 @@ export const takeTour = styled.div` } ` -export const enable = styled.div` - margin-top: 25px; -` - export const terms = styled.div` - margin: 50px 14px 15px; + margin: 32px 0 15px; color: var(--brave-palette-neutral600); - font-size: 11px; + font-size: 12px; line-height: 16px; a { @@ -94,3 +139,11 @@ export const terms = styled.div` text-decoration: none; } ` + +export const errorCode = styled.div` + color: var(--brave-palette-neutral600); + font-size: 10px; + line-height: 15px; + margin-top: 12px; + padding-bottom: 7px; +` diff --git a/components/brave_rewards/resources/shared/components/onboarding/rewards_opt_in_modal.tsx b/components/brave_rewards/resources/shared/components/onboarding/rewards_opt_in_modal.tsx index e073af135a2f..2948393b322b 100644 --- a/components/brave_rewards/resources/shared/components/onboarding/rewards_opt_in_modal.tsx +++ b/components/brave_rewards/resources/shared/components/onboarding/rewards_opt_in_modal.tsx @@ -4,43 +4,219 @@ import * as React from 'react' -import { LocaleContext } from '../../lib/locale_context' -import { Modal, ModalCloseButton } from '../modal' +import { LocaleContext, formatMessage } from '../../lib/locale_context' +import { Modal } from '../modal' import { TermsOfService } from '../terms_of_service' +import { NewTabLink } from '../new_tab_link' import { BatIcon } from '../icons/bat_icon' -import { MainButton } from './main_button' +import { LoadingIcon } from '../icons/loading_icon' +import { ErrorIcon } from './icons/error_icon' +import { SuccessIcon } from './icons/success_icon' +import { GeoPinIcon } from '../icons/geo_pin_icon' +import { CountrySelect, getCountryName } from './country_select' +import { privacyPolicyURL, contactSupportURL } from '../../lib/rewards_urls' import * as style from './rewards_opt_in_modal.style' +export type OnboardingResult = + 'success' | + 'wallet-generation-disabled' | + 'country-already-declared' | + 'unexpected-error' + +type InitialView = 'default' | 'declare-country' + interface Props { - onClose?: () => void + initialView: InitialView + result: OnboardingResult | null + availableCountries: string[] onTakeTour: () => void - onEnable: () => void + onEnable: (country: string) => void + onHideResult: () => void } export function RewardsOptInModal (props: Props) { const { getString } = React.useContext(LocaleContext) + const [countryCode, setCountryCode] = React.useState('') + const [processing, setProcessing] = React.useState(false) + const [initialView] = React.useState(props.initialView) + const [showCountrySelect, setShowCountrySelect] = + React.useState(props.initialView === 'declare-country') + + React.useEffect(() => { + setProcessing(false) + + // Only the "declare-country" view displays a success message to the user. + if (props.result === 'success' && initialView !== 'declare-country') { + props.onHideResult() + } + }, [initialView, props.result]) + + function getResultMessages (result: OnboardingResult) { + if (result === 'success') { + if (initialView === 'declare-country') { + return { + header: getString('onboardingGeoSuccessHeader'), + text: formatMessage(getString('onboardingGeoSuccessText'), [ + {getCountryName(countryCode)} + ]) + } + } + return null + } + + if (result === 'wallet-generation-disabled') { + return { + header: getString('onboardingErrorHeaderDisabled'), + text: formatMessage(getString('onboardingErrorTextDisabled'), { + tags: { + $1: (content) => + + {content} + + } + }) + } + } + + if (result === 'country-already-declared') { + return { + header: getString('onboardingErrorHeader'), + text: formatMessage(getString('onboardingErrorTextAlreadyDeclared'), { + tags: { + $1: (content) => + {content} + } + }) + } + } + + if (initialView === 'declare-country') { + return { + header: getString('onboardingErrorHeader'), + text: getString('onboardingErrorTextDeclareCountry') + } + } + + return { + header: getString('onboardingErrorHeader'), + text: getString('onboardingErrorText') + } + } + + if (props.result) { + const messages = getResultMessages(props.result) + if (!messages) { + return null + } + + return ( + + + + {props.result === 'success' ? : } + {messages.header} + + + {messages.text} + + + + + + {props.result !== 'success' && props.result} + + + + ) + } + + if (showCountrySelect) { + const onContinueClick = (event: React.UIEvent) => { + setProcessing(true) + props.onEnable(countryCode) + } + + const text = props.initialView === 'declare-country' + ? getString('onboardingGeoTextDeclareCountry') + : getString('onboardingGeoText') + + return ( + + + + {getString('onboardingGeoHeader')} + + + { + formatMessage(text, { + tags: { + $1: (content) => + + {content} + + } + }) + } + + + + + + + + + + + + + ) + } + + const onEnableClick = () => { + setShowCountrySelect(true) + } return ( - {props.onClose && } {getString('onboardingEarnHeader')} {getString('onboardingEarnText')} + + + - - - {getString('onboardingStartUsingRewards')} - - diff --git a/components/brave_rewards/resources/shared/components/onboarding/rewards_tour_modal.style.ts b/components/brave_rewards/resources/shared/components/onboarding/rewards_tour_modal.style.ts index db2f0759705c..583e83b13c26 100644 --- a/components/brave_rewards/resources/shared/components/onboarding/rewards_tour_modal.style.ts +++ b/components/brave_rewards/resources/shared/components/onboarding/rewards_tour_modal.style.ts @@ -10,7 +10,7 @@ export const root = styled.div` padding: 17px; background-color: var(--brave-palette-white); box-shadow: 0px 0px 16px rgba(99, 105, 110, 0.2); - border-radius: 8px; + border-radius: 16px; display: flex; flex-direction: column; diff --git a/components/brave_rewards/resources/shared/components/onboarding/stories/index.tsx b/components/brave_rewards/resources/shared/components/onboarding/stories/index.tsx index c36af1651a8b..1ffab1af5e20 100644 --- a/components/brave_rewards/resources/shared/components/onboarding/stories/index.tsx +++ b/components/brave_rewards/resources/shared/components/onboarding/stories/index.tsx @@ -90,9 +90,12 @@ export function OptInModal () { return ( ) diff --git a/components/brave_rewards/resources/shared/components/onboarding/stories/locale_strings.ts b/components/brave_rewards/resources/shared/components/onboarding/stories/locale_strings.ts index c40de1687081..6f4a20062b31 100644 --- a/components/brave_rewards/resources/shared/components/onboarding/stories/locale_strings.ts +++ b/components/brave_rewards/resources/shared/components/onboarding/stories/locale_strings.ts @@ -1,11 +1,26 @@ export const localeStrings = { onboardingBraveRewards: 'Brave Rewards', + onboardingClose: 'Close', + onboardingContinue: 'Continue', onboardingDetailLinks: '$1Take a quick tour$2 or $3learn more$4 for details.', onboardingEarnHeader: 'Earn Tokens & Give Back', onboardingEarnText: 'Earn tokens by viewing privacy-respecting ads and support your favorite sites and content creators automatically.', + onboardingErrorHeader: 'Something went wrong', + onboardingErrorHeaderDisabled: 'Brave Rewards not available', + onboardingErrorText: 'Unfortunately, there was an error while trying to set up Brave Rewards. Please try again.', + onboardingErrorTextAlreadyDeclared: 'It looks like a country has already been set. You can $1contact support$2 for help.', + onboardingErrorTextDeclareCountry: 'Unfortunately, there was an error while trying to set your country. Please try again.', + onboardingErrorTextDisabled: 'New signups for Brave Rewards are currently disabled in your region. However, you can always try again later. $1Learn more$2', + onboardingGeoHeader: 'Select your country', + onboardingGeoSuccessHeader: 'Thank you!', + onboardingGeoSuccessText: 'Your Brave Rewards region has been set to $1.', + onboardingGeoText: 'Select your country so we can show you the right options and ads for your region. $1Privacy Policy$2', + onboardingGeoTextDeclareCountry: 'To continue using Brave Rewards, select your country so we can show you the right options and ads for your region. $1Privacy Policy$2', onboardingMaybeLater: 'Maybe later', onboardingPromoHeader: 'Need a refresher on Rewards?', onboardingPromoText: 'Take a quick tour to brush up on how it works to go down in history as a Rewards rockstar!', + onboardingSave: 'Save', + onboardingSelectCountry: 'Select your country', onboardingSetupAdsHeader: 'How often do you want to see ads?', onboardingSetupAdsSubheader: '(ads per hour)', onboardingSetupContributeHeader: 'How much monthly support do you want to give to your favorite creators?', diff --git a/components/brave_rewards/resources/shared/lib/rewards_urls.ts b/components/brave_rewards/resources/shared/lib/rewards_urls.ts index 71feee89d8d5..bcc03d8b945c 100644 --- a/components/brave_rewards/resources/shared/lib/rewards_urls.ts +++ b/components/brave_rewards/resources/shared/lib/rewards_urls.ts @@ -7,3 +7,4 @@ export const privacyPolicyURL = 'https://brave.com/privacy/#rewards' export const settingsURL = 'chrome://rewards' export const payoutStatusURL = 'https://brave.com/payout-status' export const tippingLearnMoreURL = 'https://support.brave.com/hc/en-us/articles/360021123971-How-do-I-tip-websites-and-Content-Creators-in-Brave-Rewards-' +export const contactSupportURL = 'https://community.brave.com/' diff --git a/components/definitions/chromel.d.ts b/components/definitions/chromel.d.ts index 5ba46869b0af..d75ebca2caa9 100644 --- a/components/definitions/chromel.d.ts +++ b/components/definitions/chromel.d.ts @@ -72,14 +72,16 @@ declare namespace chrome.braveRewards { interface RewardsWallet { paymentId: string + geoCountry: string } const onRewardsWalletUpdated: { addListener: (callback: () => void) => void } - const createRewardsWallet: (callback: (errorCode?: number) => void) => void - const getRewardsWallet: (callback: (wallet?: RewardsWallet) => void) => void + const createRewardsWallet: (country: string, callback: (result?: string) => void) => void + const getAvailableCountries: (callback: (countries: string[]) => void) => void + const getDeclaredCountry: (callback: (country: string) => void) => void const getRewardsParameters: (callback: (properties: RewardsExtension.RewardsParameters) => void) => {} const updateMediaDuration: (tabId: number, publisherKey: string, duration: number, firstVisit: boolean) => {} const getPublisherInfo: (publisherKey: string, callback: (result: RewardsExtension.Result, properties: RewardsExtension.PublisherInfo) => void) => {} diff --git a/components/definitions/newTab.d.ts b/components/definitions/newTab.d.ts index ebe2646ab1b0..f03db8f7a253 100644 --- a/components/definitions/newTab.d.ts +++ b/components/definitions/newTab.d.ts @@ -173,6 +173,7 @@ declare namespace NewTab { export interface RewardsWidgetState { rewardsEnabled: boolean + declaredCountry: string adsSupported?: boolean balance: RewardsBalance externalWallet?: RewardsExtension.ExternalWallet diff --git a/components/resources/rewards_strings.grdp b/components/resources/rewards_strings.grdp index 068b5844082e..719db588f60f 100644 --- a/components/resources/rewards_strings.grdp +++ b/components/resources/rewards_strings.grdp @@ -89,6 +89,9 @@ Claim Tokens + + Continue + Earning @@ -119,6 +122,12 @@ Rewards settings + + Select your country + + + To continue using Brave Rewards, select your country so we can show you the right options and ads for your region. $1Privacy Policy$2 + You’re earning tokens for viewing this sponsored image. @@ -324,6 +333,12 @@ Brave Rewards + + Close + + + Continue + $1Take a quick tour$2 or $3learn more$4 for details. @@ -333,6 +348,39 @@ Earn tokens by viewing privacy-respecting ads and support your favorite sites and content creators automatically. + + Something went wrong + + + Brave Rewards not available + + + Unfortunately, there was an error while trying to set up Brave Rewards. Please try again. + + + It looks like a country has already been set. You can $1contact support$2 for help.', + + + Unfortunately, there was an error while trying to set your country. Please try again. + + + New signups for Brave Rewards are currently disabled in your region. However, you can always try again later. $1Learn more$2 + + + Select your country + + + Thank you! + + + Your Brave Rewards region has been set to $1United States. + + + Select your country so we can show you the right options and ads for your region. $1Privacy Policy$2 + + + To continue using Brave Rewards, select your country so we can show you the right options and ads for your region. $1Privacy Policy$2 + Maybe Later @@ -342,6 +390,12 @@ Take a quick tour to brush up on how it works to go down in history as a Rewards rockstar! + + Save + + + Select Country + How often do you want to see ads? diff --git a/components/services/bat_ledger/bat_ledger_impl.cc b/components/services/bat_ledger/bat_ledger_impl.cc index d65ad7fd0424..5fc87b5bbdab 100644 --- a/components/services/bat_ledger/bat_ledger_impl.cc +++ b/components/services/bat_ledger/bat_ledger_impl.cc @@ -48,8 +48,9 @@ void BatLedgerImpl::Initialize( std::bind(BatLedgerImpl::OnInitialize, holder, _1)); } -void BatLedgerImpl::CreateRewardsWallet(CreateRewardsWalletCallback callback) { - ledger_->CreateRewardsWallet(std::move(callback)); +void BatLedgerImpl::CreateRewardsWallet(const std::string& country, + CreateRewardsWalletCallback callback) { + ledger_->CreateRewardsWallet(country, std::move(callback)); } void BatLedgerImpl::GetRewardsParameters( diff --git a/components/services/bat_ledger/bat_ledger_impl.h b/components/services/bat_ledger/bat_ledger_impl.h index 8beef2efc91d..282899afbf79 100644 --- a/components/services/bat_ledger/bat_ledger_impl.h +++ b/components/services/bat_ledger/bat_ledger_impl.h @@ -37,7 +37,8 @@ class BatLedgerImpl : void Initialize( const bool execute_create_script, InitializeCallback callback) override; - void CreateRewardsWallet(CreateRewardsWalletCallback callback) override; + void CreateRewardsWallet(const std::string& country, + CreateRewardsWalletCallback callback) override; void GetRewardsParameters(GetRewardsParametersCallback callback) override; void GetAutoContributeProperties( diff --git a/components/services/bat_ledger/public/interfaces/bat_ledger.mojom b/components/services/bat_ledger/public/interfaces/bat_ledger.mojom index 56543a95c579..9aec87fb26c4 100644 --- a/components/services/bat_ledger/public/interfaces/bat_ledger.mojom +++ b/components/services/bat_ledger/public/interfaces/bat_ledger.mojom @@ -26,7 +26,10 @@ interface BatLedgerService { interface BatLedger { Initialize(bool execute_create_script) => (ledger.mojom.Result result); - CreateRewardsWallet() => (ledger.mojom.Result result); + + CreateRewardsWallet(string country) => + (ledger.mojom.CreateRewardsWalletResult result); + GetRewardsParameters() => (ledger.mojom.RewardsParameters properties); GetAutoContributeProperties() => (ledger.mojom.AutoContributeProperties props); diff --git a/ios/browser/api/ledger/brave_ledger.mm b/ios/browser/api/ledger/brave_ledger.mm index 884a5773d3cd..ba07454e9781 100644 --- a/ios/browser/api/ledger/brave_ledger.mm +++ b/ios/browser/api/ledger/brave_ledger.mm @@ -442,48 +442,57 @@ - (void)createWallet:(void (^)(NSError* _Nullable))completion { // malformed data // - REGISTRATION_VERIFICATION_FAILED: Missing master user token self.initializingWallet = YES; - ledger->CreateRewardsWallet(base::BindOnce(^(ledger::mojom::Result result) { - const auto strongSelf = weakSelf; - if (!strongSelf) { - return; - } - NSError* error = nil; - if (result != ledger::mojom::Result::LEDGER_OK) { - std::map errorDescriptions{ - {ledger::mojom::Result::LEDGER_ERROR, - "The wallet was already initialized"}, - {ledger::mojom::Result::BAD_REGISTRATION_RESPONSE, - "Request credentials call failure or malformed data"}, - {ledger::mojom::Result::REGISTRATION_VERIFICATION_FAILED, - "Missing master user token from registered persona"}, - }; - NSDictionary* userInfo = @{}; - const auto description = - errorDescriptions[static_cast(result)]; - if (description.length() > 0) { - userInfo = - @{NSLocalizedDescriptionKey : base::SysUTF8ToNSString(description)}; - } - error = [NSError errorWithDomain:BraveLedgerErrorDomain - code:static_cast(result) - userInfo:userInfo]; - } - - [strongSelf startNotificationTimers]; - strongSelf.initializingWallet = NO; + ledger->CreateRewardsWallet( + "", + base::BindOnce(^(ledger::mojom::CreateRewardsWalletResult create_result) { + const auto strongSelf = weakSelf; + if (!strongSelf) { + return; + } - dispatch_async(dispatch_get_main_queue(), ^{ - if (completion) { - completion(error); - } + ledger::mojom::Result result = + create_result == ledger::mojom::CreateRewardsWalletResult::kSuccess + ? ledger::mojom::Result::LEDGER_OK + : ledger::mojom::Result::LEDGER_ERROR; - for (BraveLedgerObserver* observer in [strongSelf.observers copy]) { - if (observer.walletInitalized) { - observer.walletInitalized(static_cast(result)); + NSError* error = nil; + if (result != ledger::mojom::Result::LEDGER_OK) { + std::map errorDescriptions{ + {ledger::mojom::Result::LEDGER_ERROR, + "The wallet was already initialized"}, + {ledger::mojom::Result::BAD_REGISTRATION_RESPONSE, + "Request credentials call failure or malformed data"}, + {ledger::mojom::Result::REGISTRATION_VERIFICATION_FAILED, + "Missing master user token from registered persona"}, + }; + NSDictionary* userInfo = @{}; + const auto description = + errorDescriptions[static_cast(result)]; + if (description.length() > 0) { + userInfo = @{ + NSLocalizedDescriptionKey : base::SysUTF8ToNSString(description) + }; + } + error = [NSError errorWithDomain:BraveLedgerErrorDomain + code:static_cast(result) + userInfo:userInfo]; } - } - }); - })); + + [strongSelf startNotificationTimers]; + strongSelf.initializingWallet = NO; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) { + completion(error); + } + + for (BraveLedgerObserver* observer in [strongSelf.observers copy]) { + if (observer.walletInitalized) { + observer.walletInitalized(static_cast(result)); + } + } + }); + })); } - (void)currentWalletInfo: diff --git a/vendor/bat-native-ledger/include/bat/ledger/ledger.h b/vendor/bat-native-ledger/include/bat/ledger/ledger.h index 33523a6456ed..01b237b108a4 100644 --- a/vendor/bat-native-ledger/include/bat/ledger/ledger.h +++ b/vendor/bat-native-ledger/include/bat/ledger/ledger.h @@ -32,6 +32,9 @@ using PublisherBannerCallback = std::function; using GetRewardsParametersCallback = base::OnceCallback; +using CreateRewardsWalletCallback = + base::OnceCallback; + using OnRefreshPublisherCallback = std::function; using HasSufficientBalanceToReconcileCallback = std::function; @@ -132,7 +135,8 @@ class LEDGER_EXPORT Ledger { virtual void Initialize(bool execute_create_script, LegacyResultCallback) = 0; - virtual void CreateRewardsWallet(ResultCallback callback) = 0; + virtual void CreateRewardsWallet(const std::string& country, + CreateRewardsWalletCallback callback) = 0; virtual void OneTimeTip(const std::string& publisher_key, double amount, diff --git a/vendor/bat-native-ledger/include/bat/ledger/public/interfaces/ledger.mojom b/vendor/bat-native-ledger/include/bat/ledger/public/interfaces/ledger.mojom index 0c57bc7b71af..0e9fce5ce49c 100644 --- a/vendor/bat-native-ledger/include/bat/ledger/public/interfaces/ledger.mojom +++ b/vendor/bat-native-ledger/include/bat/ledger/public/interfaces/ledger.mojom @@ -556,8 +556,14 @@ struct EventLog { uint64 created_at; }; +enum CreateRewardsWalletResult { + kSuccess, + kWalletGenerationDisabled, + kGeoCountryAlreadyDeclared, + kUnexpected +}; + struct RewardsWallet { string payment_id; array recovery_seed; - string geo_country; }; diff --git a/vendor/bat-native-ledger/src/bat/ledger/internal/ledger_impl.cc b/vendor/bat-native-ledger/src/bat/ledger/internal/ledger_impl.cc index 78c11677ca04..5c9dc72e3015 100644 --- a/vendor/bat-native-ledger/src/bat/ledger/internal/ledger_impl.cc +++ b/vendor/bat-native-ledger/src/bat/ledger/internal/ledger_impl.cc @@ -269,9 +269,13 @@ void LedgerImpl::OnStateInitialized(mojom::Result result, callback(mojom::Result::LEDGER_OK); } -void LedgerImpl::CreateRewardsWallet(ResultCallback callback) { - WhenReady([this, callback = std::move(callback)]() mutable { - wallet()->CreateWalletIfNecessary(std::move(callback)); +void LedgerImpl::CreateRewardsWallet(const std::string& country, + CreateRewardsWalletCallback callback) { + WhenReady([this, country, callback = std::move(callback)]() mutable { + wallet()->CreateWalletIfNecessary( + country.empty() ? absl::nullopt + : absl::optional(std::move(country)), + std::move(callback)); }); } @@ -935,7 +939,18 @@ bool LedgerImpl::IsShuttingDown() const { } void LedgerImpl::GetRewardsWallet(GetRewardsWalletCallback callback) { - WhenReady([this, callback]() { callback(wallet()->GetWallet()); }); + WhenReady([this, callback]() { + auto rewards_wallet = wallet()->GetWallet(); + if (rewards_wallet) { + // While the wallet creation flow is running, the Rewards wallet data may + // have a recovery seed without a payment ID. Only return a struct to the + // caller if it contains a payment ID. + if (rewards_wallet->payment_id.empty()) { + rewards_wallet = nullptr; + } + } + callback(std::move(rewards_wallet)); + }); } std::string LedgerImpl::GetRewardsWalletPassphrase() { diff --git a/vendor/bat-native-ledger/src/bat/ledger/internal/ledger_impl.h b/vendor/bat-native-ledger/src/bat/ledger/internal/ledger_impl.h index c0bcfb6bcd64..8ee76c272e44 100644 --- a/vendor/bat-native-ledger/src/bat/ledger/internal/ledger_impl.h +++ b/vendor/bat-native-ledger/src/bat/ledger/internal/ledger_impl.h @@ -96,7 +96,8 @@ class LedgerImpl : public Ledger { void Initialize(bool execute_create_script, LegacyResultCallback callback) override; - void CreateRewardsWallet(ResultCallback callback) override; + void CreateRewardsWallet(const std::string& country, + CreateRewardsWalletCallback callback) override; void OneTimeTip(const std::string& publisher_key, double amount, diff --git a/vendor/bat-native-ledger/src/bat/ledger/internal/promotion/promotion.cc b/vendor/bat-native-ledger/src/bat/ledger/internal/promotion/promotion.cc index fbf06c9afc53..f44adf5fe79c 100644 --- a/vendor/bat-native-ledger/src/bat/ledger/internal/promotion/promotion.cc +++ b/vendor/bat-native-ledger/src/bat/ledger/internal/promotion/promotion.cc @@ -285,22 +285,8 @@ void Promotion::OnClaimPromotion(ledger::ClaimPromotionCallback callback, } const auto wallet = ledger_->wallet()->GetWallet(); - if (wallet) { - attestation_->Start(payload, std::move(callback)); - return; - } - - ledger_->wallet()->CreateWalletIfNecessary( - base::BindOnce(&Promotion::OnCreateWalletIfNecessary, - base::Unretained(this), std::move(callback), payload)); -} - -void Promotion::OnCreateWalletIfNecessary( - ledger::ClaimPromotionCallback callback, - const std::string& payload, - mojom::Result result) { - if (result != mojom::Result::LEDGER_OK) { - BLOG(0, "Wallet couldn't be created"); + if (!wallet) { + BLOG(0, "Rewards wallet does not exist"); std::move(callback).Run(mojom::Result::LEDGER_ERROR, ""); return; } diff --git a/vendor/bat-native-ledger/src/bat/ledger/internal/promotion/promotion.h b/vendor/bat-native-ledger/src/bat/ledger/internal/promotion/promotion.h index a7f2d31e5e69..1b2adac3119a 100644 --- a/vendor/bat-native-ledger/src/bat/ledger/internal/promotion/promotion.h +++ b/vendor/bat-native-ledger/src/bat/ledger/internal/promotion/promotion.h @@ -74,10 +74,6 @@ class Promotion { const std::string& payload, mojom::PromotionPtr promotion); - void OnCreateWalletIfNecessary(ledger::ClaimPromotionCallback callback, - const std::string& payload, - mojom::Result result); - void OnAttestPromotion(ledger::AttestPromotionCallback callback, const std::string& solution, mojom::PromotionPtr promotion); diff --git a/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet.cc b/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet.cc index 8337dadb4240..598af595687c 100644 --- a/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet.cc +++ b/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet.cc @@ -36,8 +36,9 @@ Wallet::Wallet(LedgerImpl* ledger) Wallet::~Wallet() = default; -void Wallet::CreateWalletIfNecessary(ledger::ResultCallback callback) { - create_->CreateWallet(absl::nullopt, std::move(callback)); +void Wallet::CreateWalletIfNecessary(absl::optional&& geo_country, + CreateRewardsWalletCallback callback) { + create_->CreateWallet(std::move(geo_country), std::move(callback)); } std::string Wallet::GetWalletPassphrase(mojom::RewardsWalletPtr wallet) { @@ -89,29 +90,6 @@ void Wallet::ExternalWalletAuthorization( const std::string& wallet_type, const base::flat_map& args, ledger::ExternalWalletAuthorizationCallback callback) { - CreateWalletIfNecessary( - base::BindOnce(&Wallet::OnCreateWalletIfNecessary, base::Unretained(this), - std::move(callback), wallet_type, args)); -} - -void Wallet::OnCreateWalletIfNecessary( - ledger::ExternalWalletAuthorizationCallback callback, - const std::string& wallet_type, - const base::flat_map& args, - mojom::Result result) { - if (result != mojom::Result::LEDGER_OK) { - BLOG(0, "Wallet couldn't be created"); - callback(mojom::Result::LEDGER_ERROR, {}); - return; - } - - AuthorizeWallet(wallet_type, args, callback); -} - -void Wallet::AuthorizeWallet( - const std::string& wallet_type, - const base::flat_map& args, - ledger::ExternalWalletAuthorizationCallback callback) { if (wallet_type == constant::kWalletUphold) { ledger_->uphold()->WalletAuthorization(args, callback); return; @@ -268,10 +246,6 @@ mojom::RewardsWalletPtr Wallet::GetWallet(bool* corrupted) { vector_seed.assign(decoded_seed.begin(), decoded_seed.end()); wallet->recovery_seed = vector_seed; - if (const auto* geo_country = dict.FindString("geo_country")) { - wallet->geo_country = *geo_country; - } - return wallet; } @@ -296,7 +270,6 @@ bool Wallet::SetWallet(mojom::RewardsWalletPtr wallet) { base::Value::Dict new_wallet; new_wallet.Set("payment_id", wallet->payment_id); new_wallet.Set("recovery_seed", seed_string); - new_wallet.Set("geo_country", wallet->geo_country); std::string json; base::JSONWriter::Write(new_wallet, &json); diff --git a/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet.h b/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet.h index f59f2d1c3eb9..07327179af05 100644 --- a/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet.h +++ b/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet.h @@ -17,6 +17,7 @@ #include "bat/ledger/internal/wallet/wallet_create.h" #include "bat/ledger/internal/wallet/wallet_recover.h" #include "bat/ledger/ledger.h" +#include "third_party/abseil-cpp/absl/types/optional.h" namespace ledger { class LedgerImpl; @@ -28,7 +29,8 @@ class Wallet { explicit Wallet(LedgerImpl* ledger); ~Wallet(); - void CreateWalletIfNecessary(ledger::ResultCallback callback); + void CreateWalletIfNecessary(absl::optional&& geo_country, + CreateRewardsWalletCallback callback); void RecoverWallet(const std::string& pass_phrase, ledger::LegacyResultCallback callback); @@ -56,19 +58,9 @@ class Wallet { ledger::PostSuggestionsClaimCallback callback); private: - void AuthorizeWallet(const std::string& wallet_type, - const base::flat_map& args, - ledger::ExternalWalletAuthorizationCallback callback); - void OnClaimWallet(ledger::PostSuggestionsClaimCallback callback, mojom::Result result); - void OnCreateWalletIfNecessary( - ledger::ExternalWalletAuthorizationCallback callback, - const std::string& wallet_type, - const base::flat_map& args, - mojom::Result result); - LedgerImpl* ledger_; // NOT OWNED std::unique_ptr create_; std::unique_ptr recover_; diff --git a/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_create.cc b/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_create.cc index 23f974cf5393..0db232bfd4bd 100644 --- a/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_create.cc +++ b/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_create.cc @@ -21,12 +21,34 @@ using ledger::endpoints::RequestFor; namespace ledger::wallet { +namespace { + +mojom::CreateRewardsWalletResult MapEndpointError(PostWallets::Error error) { + switch (error) { + case PostWallets::Error::kWalletGenerationDisabled: + return mojom::CreateRewardsWalletResult::kWalletGenerationDisabled; + default: + return mojom::CreateRewardsWalletResult::kUnexpected; + } +} + +mojom::CreateRewardsWalletResult MapEndpointError(PatchWallets::Error error) { + switch (error) { + case PatchWallets::Error::kGeoCountryAlreadyDeclared: + return mojom::CreateRewardsWalletResult::kGeoCountryAlreadyDeclared; + default: + return mojom::CreateRewardsWalletResult::kUnexpected; + } +} + +} // namespace + WalletCreate::WalletCreate(LedgerImpl* ledger) : ledger_(ledger) { DCHECK(ledger_); } void WalletCreate::CreateWallet(absl::optional&& geo_country, - ResultCallback callback) { + CreateRewardsWalletCallback callback) { bool corrupted = false; auto wallet = ledger_->wallet()->GetWallet(&corrupted); @@ -42,10 +64,12 @@ void WalletCreate::CreateWallet(absl::optional&& geo_country, if (!ledger_->wallet()->SetWallet(std::move(wallet))) { BLOG(0, "Failed to set Rewards wallet!"); - return std::move(callback).Run(mojom::Result::LEDGER_ERROR); + return std::move(callback).Run( + mojom::CreateRewardsWalletResult::kUnexpected); } } else if (!wallet->payment_id.empty()) { if (geo_country) { + DCHECK(!geo_country->empty()); auto on_update_wallet = base::BindOnce( &WalletCreate::OnResult, base::Unretained(this), std::move(callback), geo_country); @@ -54,7 +78,8 @@ void WalletCreate::CreateWallet(absl::optional&& geo_country, .Send(std::move(on_update_wallet)); } else { BLOG(1, "Rewards wallet already exists."); - return std::move(callback).Run(mojom::Result::LEDGER_OK); + return std::move(callback).Run( + mojom::CreateRewardsWalletResult::kSuccess); } } @@ -70,7 +95,7 @@ template inline constexpr bool dependent_false_v = false; template -void WalletCreate::OnResult(ResultCallback callback, +void WalletCreate::OnResult(CreateRewardsWalletCallback callback, absl::optional&& geo_country, Result&& result) { if (!result.has_value()) { @@ -85,7 +110,7 @@ void WalletCreate::OnResult(ResultCallback callback, "PatchWallets::Result!"); } - return std::move(callback).Run(mojom::Result::LEDGER_ERROR); + return std::move(callback).Run(MapEndpointError(result.error())); } auto wallet = ledger_->wallet()->GetWallet(); @@ -94,11 +119,11 @@ void WalletCreate::OnResult(ResultCallback callback, DCHECK(!result.value().empty()); wallet->payment_id = std::move(result.value()); } - wallet->geo_country = geo_country ? std::move(*geo_country) : ""; if (!ledger_->wallet()->SetWallet(std::move(wallet))) { BLOG(0, "Failed to set Rewards wallet!"); - return std::move(callback).Run(mojom::Result::LEDGER_ERROR); + return std::move(callback).Run( + mojom::CreateRewardsWalletResult::kUnexpected); } if constexpr (std::is_same_v) { @@ -110,7 +135,7 @@ void WalletCreate::OnResult(ResultCallback callback, ledger_->state()->SetCreationStamp(util::GetCurrentTimeStamp()); } - std::move(callback).Run(mojom::Result::LEDGER_OK); + std::move(callback).Run(mojom::CreateRewardsWalletResult::kSuccess); } } // namespace ledger::wallet diff --git a/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_create.h b/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_create.h index 9ec7c8e39520..eac9ee08abee 100644 --- a/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_create.h +++ b/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_create.h @@ -20,11 +20,12 @@ class WalletCreate { public: explicit WalletCreate(LedgerImpl*); - void CreateWallet(absl::optional&& geo_country, ResultCallback); + void CreateWallet(absl::optional&& geo_country, + CreateRewardsWalletCallback callback); private: template - void OnResult(ResultCallback, + void OnResult(CreateRewardsWalletCallback, absl::optional&& geo_country, Result&&); diff --git a/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_unittest.cc b/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_unittest.cc index 410ebaa6c673..5f3e452a74ed 100644 --- a/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_unittest.cc +++ b/vendor/bat-native-ledger/src/bat/ledger/internal/wallet/wallet_unittest.cc @@ -21,7 +21,7 @@ namespace ledger { class WalletTest : public BATLedgerTest { protected: - mojom::Result CreateWalletIfNecessary() { + mojom::CreateRewardsWalletResult CreateWalletIfNecessary() { auto* ledger = GetLedgerImpl(); auto response = mojom::UrlResponse::New(); @@ -34,12 +34,14 @@ class WalletTest : public BATLedgerTest { mojom::UrlMethod::POST, std::move(response)); base::RunLoop run_loop; - mojom::Result result; + mojom::CreateRewardsWalletResult result; ledger->wallet()->CreateWalletIfNecessary( - base::BindLambdaForTesting([&result, &run_loop](mojom::Result r) { - result = r; - run_loop.Quit(); - })); + absl::nullopt, + base::BindLambdaForTesting( + [&result, &run_loop](mojom::CreateRewardsWalletResult r) { + result = r; + run_loop.Quit(); + })); run_loop.Run(); return result; @@ -73,23 +75,21 @@ TEST_F(WalletTest, CreateWallet) { // Create a wallet when there is no current wallet information. GetTestLedgerClient()->SetStringState(state::kWalletBrave, ""); - mojom::Result result = CreateWalletIfNecessary(); - EXPECT_EQ(result, mojom::Result::LEDGER_OK); + mojom::CreateRewardsWalletResult result = CreateWalletIfNecessary(); + EXPECT_EQ(result, mojom::CreateRewardsWalletResult::kSuccess); mojom::RewardsWalletPtr wallet = ledger->wallet()->GetWallet(); ASSERT_TRUE(wallet); EXPECT_TRUE(!wallet->payment_id.empty()); EXPECT_TRUE(!wallet->recovery_seed.empty()); - EXPECT_TRUE(wallet->geo_country.empty()); // Create a wallet when there is corrupted wallet information. GetTestLedgerClient()->SetStringState(state::kWalletBrave, "BAD-DATA"); result = CreateWalletIfNecessary(); - EXPECT_EQ(result, mojom::Result::LEDGER_OK); + EXPECT_EQ(result, mojom::CreateRewardsWalletResult::kSuccess); wallet = ledger->wallet()->GetWallet(); ASSERT_TRUE(wallet); EXPECT_TRUE(!wallet->payment_id.empty()); EXPECT_TRUE(!wallet->recovery_seed.empty()); - EXPECT_TRUE(wallet->geo_country.empty()); } } // namespace ledger